On this page
  1. Implementations
  2. Parameters
  3. Related
  4. Games
  5. References
  6. Sources

Games keep a status bar fixed over a scrolling playfield in several ways. On the NES, the program can change scroll after a sprite 0 hit1, wait for a mapper’s scanline IRQ2, or draw the meters as sprites over the playfield3. The Game Boy’s window layer has its own tile map and does not scroll with the background4.

#Implementations

#NES

Super Mario Bros. places sprite 0 behind the background under the status bar, then its NMI handler waits for the hit before writing the level scroll5,6. The Legend of Zelda also waits for sprite 0 beneath the map and item bar; during vertical room scrolling, it writes PPUADDR mid-frame to set the lower area’s position7.

Super Mario Bros. 3 loads the MMC3 scanline counter with 192 scanlines8 each frame. Its IRQ selects the status bar’s nametable rows and tile banks8.

Super Mario World uses a different SNES interrupt setup in Mode 7 boss rooms. The top of the frame uses Mode 1 for the status bar; an IRQ changes to Mode 7 and writes its scroll below the bar9. The bar ends at line 36 in most of those rooms and line 45 in Morton and Roy’s rooms, and Bowser’s room skips the bar IRQ9.

#Game Boy

The Legend of Zelda: Link’s Awakening places its inventory bar in the window at line 128, leaving the bottom 16 lines for it10. The 1993 release sets window X to 6; the DX disassembly sets it to 710. Metroid II: Return of Samus sets the window to line 136 for the HUD’s bottom 8 lines and raises it to line 128 for save and item messages11. In the Queen’s room, the HUD moves to the background because her head uses the window11.

#Meters without a split

Mega Man 2 draws each energy meter as a column of 7 sprites, so it does not change the playfield scroll3.

Interactive: raster-split (needs JavaScript)

One frame, split into bands
  • Sprite overlayNESMega Man 2 (1988). No split screen; the energy meters are drawn as sprites.3
  • Sprite 0 hitNESSuper Mario Bros. (1985). The status bar holds still while the level scrolls under it.6,5The Legend of Zelda (1986). Waits for sprite 0 under the map and item bar; scrolls vertically between rooms by rewriting PPUADDR mid-frame.7
  • Scanline IRQNES, SNESSuper Mario Bros. 3 (1988). The MMC3's scanline counter fires an IRQ above the status bar.8Super Mario World (1990). In the Mode 7 boss rooms but Bowser's an interrupt at line 36 switches from Mode 1 to Mode 7 below the bar.9
  • Window layerGame Boy familyMetroid II: Return of Samus (1991). The HUD is the window's bottom 8 lines, raised to 16 for save and item messages; the Queen's head takes the window in her room.11The Legend of Zelda: Link's Awakening (1993). The inventory bar is the window at line 128, the bottom 16 lines.10

#Parameters

Super Mario Bros. sets sprite 0 to Y $18, tile $FF, attributes $23, X $585, behind the background, so the hit can fire without the sprite showing5. The handler first waits for the previous frame’s flag to clear, then spins on bit 6 of $2002, then runs a short delay before writing the scroll6.

Games

GameYearHowNote
Mega Man 21988Sprite overlay NESno split screen; the energy meters are drawn as sprites
Super Mario Bros.1985Sprite 0 hit NESthe status bar holds still while the level scrolls under it
The Legend of Zelda1986Sprite 0 hit NESwaits for sprite 0 under the map and item bar; scrolls vertically between rooms by rewriting PPUADDR mid-frame
Super Mario Bros. 31988Scanline IRQ NES, SNESthe MMC3's scanline counter fires an IRQ above the status bar
Super Mario World1990Scanline IRQ NES, SNESin the Mode 7 boss rooms but Bowser's an interrupt at line 36 switches from Mode 1 to Mode 7 below the bar
Metroid II: Return of Samus1991Window layer Game Boy familythe HUD is the window's bottom 8 lines, raised to 16 for save and item messages; the Queen's head takes the window in her room
The Legend of Zelda: Link's Awakening1993Window layer Game Boy familythe inventory bar is the window at line 128, the bottom 16 lines
Every claim about this mechanic, by game (13)
Super Mario Bros. 1985Each frame the NMI handler waits for the sprite 0 flag to clear, spins until the sprite 0 hit, waits a short loop for horizontal blank, and only then writes the level's scroll, so the status bar above the hit stays still.Code
The game sets sprite 0 to OAM bytes $18, $FF, $23, $58, so tile $FF sits at Y $18 and X $58 behind the background in sprite palette 3.Code
The Legend of Zelda 1986When a mode needs the split, the NMI handler waits for the sprite 0 hit under the status bar and then sets the scroll for the room below; while the screen scrolls vertically between rooms it rewrites PPUADDR ($2006) mid-frame instead of PPUSCROLL.Code
Mega Man 2 1988Mega Man 2 draws its energy meters as sprites, not a split screen; each meter is a column of 7 sprites from Y $18 down to $48, each showing 0-4 units, at X $10 for the weapon, $18 for life, and $28 for a boss.Code
Super Mario Bros. 3 1988Each frame the NMI handler loads the MMC3's scanline counter with 192, and the IRQ it raises points the PPU at the status bar's nametable rows and swaps in the status bar's tile banks.Code
Super Mario World 1990In the Mode 7 boss rooms other than Bowser's, the NMI sets Mode 1 for the status bar at the top of the frame, and an interrupt at the line where the bar ends switches to Mode 7 and writes the Mode 7 layer's scroll; the bar ends at line 36, or 45 in Morton's and Roy's rooms. Bowser's room skips the status bar interrupt and goes straight to Mode 7.Code
Metroid II: Return of Samus 1991The HUD is the window layer, set to X 7. The main game mode's handler sets window Y to $88 (line 136, the bottom 8 lines), or $80 (16 lines) to show a message while Samus touches a save point or collects a major item. In the Queen's room the HUD moves to the background layer, because her head uses the window.Code
The Legend of Zelda: Link's Awakening 1993The inventory bar along the bottom of the screen is the window layer. Setting it up stores window Y $80 (line 128), which leaves the bar the bottom 16 lines, and window X 7 in DX (6 in the 1993 release, one pixel further left); each gameplay frame copies the stored Y to the window register.Code
generalThe Game Boy's video RAM holds two 32 by 32 tile maps, each 256 by 256 pixels, and either can be shown as the background or the window; the screen shows 160 by 144 of those pixels.Wiki
The window is a second background layer whose top-left corner sits at screen position (WX − 7, WY), set by WY and WX ($FF4A-$FF4B); it can't be scrolled and always starts from its tile map's top-left tile. From the line where LY equals WY, the PPU switches from the background to the window when the line reaches WX, drawing the window's current row, and that row advances once for each line the window is drawn on; at WX 7 and WY 0 it covers the whole background.Wiki
A sprite 0 hit happens when an opaque pixel of sprite 0 overlaps an opaque background pixel; it sets bit 6 of PPUSTATUS ($2002), which stays set until the pre-render line, so a frame reports only its first hit.Wiki
The MMC3 mapper counts scanlines by watching the PPU's address line A12, reloads its counter from a latch at zero, and raises an IRQ when the counter reaches zero with IRQs enabled.Wiki
The SNES PPU draws up to four background layers in one of eight modes; Mode 1, the most common, has two 16-color layers (BG1 and BG2) and a 4-color BG3 that can be raised above them, often for a HUD or text box.Wiki

References

  1. ^A sprite 0 hit happens when an opaque pixel of sprite 0 overlaps an opaque background pixel; it sets bit 6 of PPUSTATUS ($2002), which stays set until the pre-render line, so a frame reports only its first hit.WikiNESdev Wiki, “PPU OAM”
    How we know
    While the PPU is drawing the picture, when an opaque pixel of sprite 0 overlaps an opaque pixel of the background, this is a sprite 0 hit. … Bit 6 of PPUSTATUS ($2002) is cleared to 0 at dot 1 of the pre-render line. This means only the first sprite 0 hit in a frame can be detected.
    Sprite 0 hits · archived copy ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  2. ^The MMC3 mapper counts scanlines by watching the PPU's address line A12, reloads its counter from a latch at zero, and raises an IRQ when the counter reaches zero with IRQs enabled.WikiNESdev Wiki, “MMC3”
    How we know
    The MMC3 scanline counter is based entirely on PPU A12 … When the IRQ is clocked (filtered A12 0→1), the counter value is checked - if zero or the reload flag is true, it's reloaded with the IRQ latched value at $C000; otherwise, it decrements. … If the IRQ counter is zero and IRQs are enabled ($E001), an IRQ is triggered.
    IRQ Specifics · archived copy ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  3. ^ abcMega Man 2 draws its energy meters as sprites, not a split screen; each meter is a column of 7 sprites from Y $18 down to $48, each showing 0-4 units, at X $10 for the weapon, $18 for life, and $28 for a boss.Codelsmmega/mm2
    How we know
    473	vertical_bar_base_ycoord = $18
    474	hp_bar_xcoord            = $18
    475	weapon_bar_xcoord        = $10
    476	boss_bar_xcoord          = $28
    
    home/sprites.asm lines 473–476 at 94f0bf3 · upstream ↗
    530_draw_bar_tile:
    531	LDY #6
    ⋮
    533@loop:
    534	LDA @vertical_bar_ycoord_table, Y
    535	STA aoam_y, X
    536	SEC
    537	LDA z:z00
    538	SBC #4
    
    home/sprites.asm lines 530–545 at 94f0bf3 · upstream ↗
    572@vertical_bar_ycoord_table:
    573	.BYTE vertical_bar_base_ycoord + 0
    ⋮
    579	.BYTE vertical_bar_base_ycoord + 48
    
    home/sprites.asm lines 572–579 at 94f0bf3 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  4. ^The window is a second background layer whose top-left corner sits at screen position (WX − 7, WY), set by WY and WX ($FF4A-$FF4B); it can't be scrolled and always starts from its tile map's top-left tile. From the line where LY equals WY, the PPU switches from the background to the window when the line reaches WX, drawing the window's current row, and that row advances once for each line the window is drawn on; at WX 7 and WY 0 it covers the whole background.WikiPan Docs, “Pan Docs: Tile Maps” · Pan Docs, “Pan Docs: Window”revised
    How we know
    The content of the Window is not scrollable; it is always displayed starting at the top left tile of its tile map. The only way to adjust the Window is by modifying its position on the screen, which is done via the WX and WY registers. The screen coordinates of the top left corner of the Window are (WX-7,WY).
    Window · archived copy ↗
    These two registers specify the on-screen coordinates of the Window’s top-left pixel. The Window is visible (if enabled) when WX and WY are in the range [0; 166] and [0; 143] respectively. Values WX=7, WY=0 place the Window at the top left of the screen, completely covering the background.
    FF4A–FF4B — WY, WX · archived copy ↗
    At the beginning of each scanline, if the value of WY is equal to LY, the Y condition becomes true (and remains so for subsequent scanlines). … When this counter is equal to WX, if the Y condition is true and the Window enable bit is set in LCDC, background rendering is reset, beginning anew from the active row of the Window’s tilemap. The coordinate of the active Window row is then incremented.
    Window rendering criteria · archived copy ↗
    If intending to hide the Window for part of the screen (e.g. to have a status bar at the top of the screen instead of the bottom), hide it by setting WX to a high value rather than writing to LCDC.
    Window mid-frame behavior · archived copy ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Revised after a check by a second AI model, 2026-10-21.

    In the claim ledger

  5. ^ abcdThe game sets sprite 0 to OAM bytes $18, $FF, $23, $58, so tile $FF sits at Y $18 and X $58 behind the background in sprite palette 3.CodeXkeeper0/smb1
    How we know
    2118Sprite0Data:
    2119	.db $18, $ff, $23, $58
    
    src/prg.asm lines 2118–2119 at 1cc3c92 · upstream ↗
    2237	LDY #$03                                     ; set up sprite #0
    2238ISpr0Loop:
    2239	LDA Sprite0Data,y
    2240	STA Sprite_Data,y
    ⋮
    2245	INC Sprite0HitDetectFlag                     ; set sprite #0 check flag
    
    src/prg.asm lines 2236–2245 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  6. ^ abcEach frame the NMI handler waits for the sprite 0 flag to clear, spins until the sprite 0 hit, waits a short loop for horizontal blank, and only then writes the level's scroll, so the status bar above the hit stays still.CodeXkeeper0/smb1
    How we know
    159	LDA Sprite0HitDetectFlag                     ; check for flag here
    ⋮
    162	LDA PPU_STATUS                               ; wait for sprite 0 flag to clear, which will
    163	AND #%01000000                               ; not happen until vblank has ended
    ⋮
    170Sprite0Hit:
    171	LDA PPU_STATUS                               ; do sprite #0 hit detection
    172	AND #%01000000
    173	BEQ Sprite0Hit
    174	LDY #$14                                     ; small delay, to wait until we hit horizontal blank time
    ⋮
    179	LDA HorizontalScroll                         ; set scroll registers from variables
    180	STA PPU_SCROLL_REG
    ⋮
    182	STA PPU_SCROLL_REG
    
    src/prg.asm lines 159–183 at 1cc3c92 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  7. ^ abWhen a mode needs the split, the NMI handler waits for the sprite 0 hit under the status bar and then sets the scroll for the room below; while the screen scrolls vertically between rooms it rewrites PPUADDR ($2006) mid-frame instead of PPUSCROLL.Codealdonunez/zelda1-disassembly
    How we know
    447    ; Wait for Sprite 0 hit, if needed.
    448    LDA IsSprite0CheckActive
    449    BEQ @CheckScroll
    450    LDA #$05
    451    JSR SwitchBank
    452    JSR WaitAndScrollToSplitBottom
    
    src/Z_07.asm lines 447–452 at 50a1c86 · upstream ↗
    1263WaitAndScrollToSplitBottom:
    1264    LDA PpuStatus_2002          ; Wait for Sprite 0 Hit.
    1265    AND #$40
    1266    BEQ WaitAndScrollToSplitBottom
    
    src/Z_05.asm lines 1263–1266 at 50a1c86 · upstream ↗
    1299    ; Scrolling vertically, will set PPUADDR instead of PPUSCROLL.
    1300    ; Wait about 666 cycles.
    ⋮
    1313    ; Set PPUADDR mid-frame to 
    1314    ; achieve split-frame scrolling.
    1315    LDA VScrollAddrHi
    1316    LDY VScrollAddrLo
    1317    STA PpuAddr_2006
    1318    STY PpuAddr_2006
    
    src/Z_05.asm lines 1299–1320 at 50a1c86 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  8. ^ abcEach frame the NMI handler loads the MMC3's scanline counter with 192, and the IRQ it raises points the PPU at the status bar's nametable rows and swaps in the status bar's tile banks.CodeSouthbird's SMB3 disassembly
    How we know
    1595	; This sets the status bar scroll fix for everything after the title screen!
    1596	; At scanline 192, the name table scroll is fixed to always display the status bar
    1597	LDA #192		; A = 192
    1598	STA MMC3_IRQCNT		; Store 192 into the IRQ count
    1599	STA MMC3_IRQLATCH	; Store it into the latch (will be used later)
    1600	STA MMC3_IRQENABLE	; Start the IRQ counter
    1601	CLI		; Enable maskable interrupts
    
    PRG/prg031.asm lines 1595–1601 at 09b1bd8 · upstream ↗
    2197	; Because vertical scroll will not change after frame begins (second write to
    2198	; PPU_SCROLL will always be unused until next frame), the hack for MMC3 split
    2199	; vertical scrolling is to change the nametable address that the PPU is reading
    2200	; at to where we would like it to be...
    2201	LDY #$07
    2202	STY PPU_VRAM_ADDR
    2203	STX PPU_VRAM_ADDR	; ... so we're now reading at $0700
    
    PRG/prg031.asm lines 2197–2203 at 09b1bd8 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  9. ^ abcIn the Mode 7 boss rooms other than Bowser's, the NMI sets Mode 1 for the status bar at the top of the frame, and an interrupt at the line where the bar ends switches to Mode 7 and writes the Mode 7 layer's scroll; the bar ends at line 36, or 45 in Morton's and Roy's rooms. Bowser's room skips the status bar interrupt and goes straight to Mode 7.CodeSMWDisXrevised
    How we know
    379Mode7Lagging:                                 ; Transfer various RAM mirrors to the registers
    380    LDA.B #!HW_BG_BG3Pri|!HW_BG_Mode1
    381    STA.W HW_BGMODE
    
    bank_00.asm lines 379–381 at 30643c7 · upstream ↗
    413    LDA.W IRQNMICommand                       ;\ 
    414    LSR A                                     ;| Branch if not in Bowser's room.
    415    BCC +                                     ;/
    ⋮
    420    LDA.B #!HW_TIMEN_NMI|!HW_TIMEN_JoyRead    ;\ Skip the status bar IRQ and immediately prepare the registers after.
    421    JMP CODE_0083F3                           ;/
    
    bank_00.asm lines 413–421 at 30643c7 · upstream ↗
    432    LDY.B #!StatusBarHeight+9                 ;/ Scanline the status bar ends at in Morton/Roy's rooms.
    433  + JMP CODE_008294                           ; Prepare IRQ, and set up a couple more registers.
    
    bank_00.asm lines 423–433 at 30643c7 · upstream ↗
    505CODE_0083F3:                                  ; IRQ done; wait for H-blank, then update registers.
    ⋮
    510    LDA.B #!HW_BG_Mode7
    511    STA.W HW_BGMODE
    512    LDA.B Mode7XPos
    513    STA.W HW_BG1HOFS
    ⋮
    515    STA.W HW_BG1HOFS
    ⋮
    517    STA.W HW_BG1VOFS
    ⋮
    519    STA.W HW_BG1VOFS
    
    bank_00.asm lines 505–519 at 30643c7 · upstream ↗
    57!StatusBarHeight = 36
    
    constants.asm line 57 at 30643c7 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Revised after a check by a second AI model, 2026-10-21.

    In the claim ledger

  10. ^ abcThe inventory bar along the bottom of the screen is the window layer. Setting it up stores window Y $80 (line 128), which leaves the bar the bottom 16 lines, and window X 7 in DX (6 in the 1993 release, one pixel further left); each gameplay frame copies the stored Y to the window register.Codezladx/LADX-Disassembly · “Legend of Zelda, The - Link's Awakening (USA, Europe) (Rev 2)”
    How we know
    825InitializeInventoryBar::
    826    ; Set the window to the bottom of the screen
    
    src/code/bank1.asm lines 825–830 at 2a5c2c0 · upstream ↗
    238.renderInteractiveFrame
    ⋮
    240    ; Update graphics registers from game values
    
    src/code/home/render_loop.asm lines 238–242 at 2a5c2c0 · upstream ↗

    In the cartridge (“Legend of Zelda, The - Link's Awakening (USA, Europe) (Rev 2)”): the 1993 release's setup, bank 1: ld a,$80; ld [$DB9A],a; ld a,$06; ldh [$4B],a; the 1993 release's per-frame copy, bank 0: ld a,[$DB9A]; ldh [$4A],a. We don't reproduce the bytes.

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

  11. ^ abcThe HUD is the window layer, set to X 7. The main game mode's handler sets window Y to $88 (line 136, the bottom 8 lines), or $80 (16 lines) to show a message while Samus touches a save point or collects a major item. In the Queen's room the HUD moves to the background layer, because her head uses the window.Codealex-west/M2RoS
    How we know
    2946; Handle window height, save text, earthquake, low heath beep, fade in, and Metroid Queen cry
    ⋮
    2970        ld a, $88 ; Default window position
    2971        ldh [rWY], a
    ⋮
    2981                cp $0b ; Check if not a common item or refill
    ⋮
    2983                    ld a, $80 ; Higher window position
    2984                    ldh [rWY], a
    ⋮
    2987            ; Touching a save point
    2988            ld a, $80 ; Higher window position
    2989            ldh [rWY], a
    
    SRC/bank_001.asm lines 2946–2989 at 031aea3 · upstream ↗
    72    ; (the HUD in the Queen's room is on the normal BG layer, since her head uses the window layer)
    ⋮
    77        ld a, $07
    78        ldh [rWX], a
    79        ld hl, vramDest_statusBar
    
    SRC/bank_001.asm lines 71–79 at 031aea3 · upstream ↗
    689    ; Handle window height, save sprite text, earthquake, low heath beep, fade in, and Metroid Queen cry
    690    call miscIngameTasks_longJump
    
    SRC/bank_000.asm lines 683–690 at 031aea3 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Checked by a second AI model, 2026-10-21.

    In the claim ledger

Sources

Code

Secondary