Sprite flicker
Changing the order of sprites in OAM from frame to frame so that, past the 8-per-scanline limit, a different sprite drops out each frame.
On this page
The PPU shows up to 64 sprites1 and finds each scanline’s sprites by searching OAM in order, keeping at most 8 sprites per scanline2. Sprites late in OAM order lose on a crowded line2. A game that changes the order each frame spreads the loss across sprites, so each one blinks instead of one vanishing.
- Alternating orderMega Man 2 (1988). Crowded sprites take turns, so they flicker instead of vanishing.3
- Rotating slotsSuper Mario Bros. (1985). When too many sprites share a line, a different one drops out each frame, so they flicker in turn.4
#Parameters
Super Mario Bros. leaves the first OAM slots, sprite 0 among them, in place and shuffles only the objects above them4. Mega Man 2 flips the whole list every other frame3.
Games
| Game | Year | How | Note |
|---|---|---|---|
| Mega Man 2 | 1988 | Alternating order | crowded sprites take turns, so they flicker instead of vanishing |
| Super Mario Bros. | 1985 | Rotating slots | when too many sprites share a line, a different one drops out each frame, so they flicker in turn |
Every claim about this mechanic, by game (5)
References
- ^The PPU's object attribute memory (OAM) holds a display list of up to 64 sprites, 4 bytes each, and sprite 0 is the one at OAM bytes
$00-$03.WikiNESdev Wiki, “PPU OAM”How we know
contains a display list of up to 64 sprites, where each sprite's information occupies 4 bytes.
lead · archived copy ↗ Sprite 0 is the sprite controlled by OAM addresses $00-$03
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.
- ^ abOn each scanline the PPU copies the sprites in range, searching OAM in order, into a secondary OAM that holds 8, so a scanline shows at most 8 sprites and the later ones in OAM order drop out.WikiNESdev Wiki, “PPU OAM”
How we know
the PPU contains 32 bytes (enough for 8 sprites) of secondary OAM memory … a linear search of the entire primary OAM is carried out to find sprites that are within y range for the next scanline
Internal operation · archived copy ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abOn even frames
_spriteswrites Mega Man's object slots (0 to$0F), then the enemies' ($10to$1F), then the energy meters; on odd frames it writes the meters first and the slots in reverse,$1Fdown to$10, then$0Fdown to 0, so objects that lose the 8-per-scanline race lose it only every other frame.Codelsmmega/mm2revisedHow we know
12@time_stopper_flag_not_set: 13 LDA z:znmi_frame 14 AND #%00000001 15 BNE @invert ⋮ 21@loop_1: 22 JSR _draw_megaman_with_animation ⋮ 26 CMP #$10 27 BNE @loop_1 ⋮ 29@loop_2: 30 JSR _draw_enemies_with_animation ⋮ 34 CMP #$20 35 BNE @loop_2 ⋮
home/sprites.asm lines 12–38 at 94f0bf3 · upstream ↗ 43@invert: 44 JSR _draw_bar ⋮ 47 LDA #$1F 48 STA z:zsprites_pointer ⋮ 50@loop_3: 51 JSR _draw_enemies_with_animation 52 BCS @max_2 53 DEC z:zsprites_pointer ⋮ 58@loop_4: 59 JSR _draw_megaman_with_animation 60 BCS @max_2 61 DEC z:zsprites_pointer ⋮
home/sprites.asm lines 43–62 at 94f0bf3 · 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.
- ^ abEvery unpaused frame
SpriteShuffleradds one of three amounts,$58,$48, or$38in turn, to each object's OAM offset at or above$28, wrapping within$28-$FF, so the objects' sprites change OAM slots each frame and a ninth sprite on a line drops out on different frames.CodeXkeeper0/smb1How we know
165 LDA GamePauseStatus ; if in pause mode, do not bother with sprites at all ⋮ 168 JSR MoveSpritesOffscreen 169 JSR SpriteShuffler
src/prg.asm lines 164–169 at 1cc3c92 · upstream ↗ 240SpriteShuffler: ⋮ 242 LDA #$28 ; load preset value which will put it at 243 STA $00 ; sprite #10 244 LDX #$0e ; start at the end of OAM data offsets 245ShuffleLoop: 246 LDA SprDataOffset,x ; check for offset value against 247 CMP $00 ; the preset value 248 BCC NextSprOffset ; if less, skip this part 249 LDY SprShuffleAmtOffset ; get current offset to preset value we want to add 250 CLC 251 ADC SprShuffleAmt,y ; get shuffle amount, add to current sprite offset 252 BCC StrSprOffset ; if not exceeded $ff, skip second add ⋮
src/prg.asm lines 240–266 at 1cc3c92 · upstream ↗ 2225 LDA #$38 ; load sprite shuffle amounts to be used later 2226 STA SprShuffleAmt+2 2227 LDA #$48 2228 STA SprShuffleAmt+1 2229 LDA #$58 2230 STA SprShuffleAmt
src/prg.asm lines 2225–2230 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.
Sources
Code
- doppelganger and Xkeeper. 2022. “smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1).” Commit 1cc3c92. Xkeeper0/smb1 @ 1cc3c92 · licence, all uses
- lsmmega. 2026. “mm2: Mega Man 2 disassembly (lsmmega/mm2).” Commit 94f0bf3. lsmmega/mm2 @ 94f0bf3 · licence, all uses
Secondary
- NESdev Wiki contributors. n.d. “PPU OAM.” NESdev Wiki. Version oldid 23161. original · archived · licence, all uses