Why NES Sprites Flicker: The Eight-Per-Scanline Limit, and What SMB3 Actually Did With It

Why NES Sprites Flicker: The Eight-Per-Scanline Limit, and What SMB3 Actually Did With It

Put more than eight sprites on a single NES scanline and something has to give. What gives is not always the same sprite, and that inconsistency is the whole story. The flicker people remember from the NES is not a rendering bug and it is not the console straining under load. It is a fixed buffer filling up, plus a scheduling decision made by whoever wrote the game.

This piece separates the two. The hardware limit is documented and derivable. The scheduling is software, and it varies by title. Super Mario Bros. 3 is worth examining, but only for the trick the sources actually document — not for a rotation schedule nobody has published.

Eight is arithmetic, not a design budget

The PPU keeps its sprite list in primary OAM: 64 sprites, four bytes each, 256 bytes total. That is the entire cast available for a frame. Per scanline, the PPU draws from a second, smaller memory. NESdev describes it plainly: the PPU contains “32 bytes (enough for 8 sprites) of secondary OAM memory that is not directly accessible by the program,” and that buffer is used to initialize eight internal sprite output units (PPU OAM).

Thirty-two divided by four is eight. The limit is not a policy someone chose to keep costs down, and it is not a performance target. It is the size of a buffer divided by the size of a record. That distinction matters because it predicts the failure mode: the ninth sprite is not judged unworthy or skipped for speed. There is simply nowhere to put it.

The evaluation walk, in order

Each visible scanline, the PPU runs sprite evaluation for the next line. NESdev’s sequence is worth quoting because the ordering is the entire mechanism:

First, it clears the list of sprites to draw. Second, it reads through OAM, checking which sprites will be on this scanline. It chooses the first eight it finds that do.

Then the buffer closes. From the same page: “If exactly 8 sprites have been found, disable writes to secondary OAM because it is full. This causes sprites in back to drop out” (PPU sprite evaluation).

Two details in that walk are easy to skim past and expensive to forget. First, the search runs in OAM address order, starting from sprite 0. Second, the walk continues after the buffer fills — the PPU keeps scanning, but the writes are ignored. The later sprites are not invisible to the hardware. They are simply not copied.

There is also a one-line offset baked into the pipeline. Sprite evaluation does not happen on the pre-render scanline, and because evaluation applies to the next line’s rendering, no sprites appear on the first visible scanline. That is why a sprite’s Y coordinate has to be written one less than its intended screen position (PPU sprite evaluation).

OAM order is also the priority list

Here is where the limit stops being a simple capacity problem. The order the PPU walks OAM is the same order that decides what draws in front of what. NESdev: “Priority between sprites is determined by their address inside OAM. So to have a sprite displayed in front of another sprite in a scanline, the sprite data that occurs first will overlap any other sprites after it” (PPU OAM).

So the sprite list is doing double duty. It is the evaluation queue and the draw order at the same time. Reordering it is not a cosmetic operation. Move a sprite earlier and you have changed both whether it survives the eight-slot cut and who it covers when it does.

That has a consequence worth stating explicitly, because it is where a lot of casual explanations go wrong: rotation schemes trade dropout for priority churn. If you shuffle the list every frame to share the slots, you are also shuffling who is in front. Any rotation policy has to be evaluated on both axes, not just on how many sprites reach the screen.

Flicker is a scheduling policy over a fixed resource

Take a line with twelve sprites and no reordering. The first eight in OAM order are drawn every frame. The last four are drawn on no frame. That is permanent dropout, and it looks like missing objects rather than flicker.

Now rewrite the order each frame. With twelve sprites competing for eight slots, each sprite is drawn on eight of every twelve frames — two-thirds of the time, assuming an even rotation. The total number of sprites reaching the screen per frame does not change. Eight is still eight. What changes is which eight, and the visual result is intermittent absence rather than permanent absence.

That is the honest framing: rotation is a fairness mechanism, not a throughput gain. It redistributes the same fixed resource across frames. The arithmetic above is a hypothetical worked from the documented buffer size, not a measurement of any particular game.

What SMB3 actually did, per the sources

Super Mario Bros. 3 is often cited in flicker discussions, and it deserves a precise citation rather than a vague one. What NESdev documents is a priority-bit trick, not a rotation schedule.

The PPU has a per-sprite priority bit relative to the background, and the sprite priority page describes how SMB3 uses it for power-ups emerging from blocks: the game puts “a non-transparent back-priority sprite ‘behind’ the block at a low index and putting the power-up at a higher index” (PPU sprite priority). Because the low-index sprite wins the sprite-to-sprite contest regardless of its background priority, it can mask the power-up while the background shows through where the block is. The power-up keeps front priority and can be revealed without requiring the area above the block to be pure background pixels.

That is a genuinely clever use of the priority quirk, and it is documented. What is not documented in these sources is SMB3’s per-frame sprite scheduling — whether it rotated the whole list, rotated per object, or used some other policy. The honest position is that the priority trick is established and the scheduling is not. Anyone who tells you SMB3 rotated its OAM by one position per frame is telling you something the primary references do not say.

The broader lesson from the priority page is that the NES priority model is not the intuitive one. It is not “front sprites, then background, then back sprites.” The PPU selects the frontmost opaque sprite pixel first, then applies the background priority bit. A back-priority sprite at a low index can therefore hide a front-priority sprite at a higher index wherever it has a non-transparent pixel. Games that understood this could do things the naive model says are impossible.

The overflow flag will not save you

There is a hardware bit intended to report exactly this condition: the sprite overflow flag in PPUSTATUS. It does not work as intended. NESdev describes the evaluation logic continuing past the eighth sprite and then scanning OAM “diagonally,” evaluating tile number, attributes, and X coordinates as if they were Y coordinates. The result: “inconsistent sprite overflow behavior showing both false positives and false negatives” (PPU sprite evaluation).

The practical implication is direct. Software cannot outsource per-scanline limit detection to the hardware. A game that needs to know whether it has exceeded eight sprites on a line has to track that itself, in its own bookkeeping, before the frame is assembled. The flag is a curiosity, not a tool.

What this means if you are writing per-scanline sprite code

Three things follow from the mechanism, and they are recommendations rather than findings:

Treat OAM order as your scheduling surface. It is the only lever that controls both survival and priority. Decide deliberately which sprites get the early slots, and decide whether that assignment is static or changes per frame. Do not let it fall out of whatever order your object update loop happened to produce.

Budget per scanline, not per frame. Sixty-four sprites in OAM is a frame-level number. The constraint that produces visible artifacts is eight per line. A game can be comfortably under 64 total and still drop sprites on a crowded horizontal band.

Do not rely on the overflow flag. If you need to know, count it yourself. The hardware’s detector is documented as unreliable, and building logic around it will produce behavior you cannot debug from the screen alone.

The flicker, in the end, is not the NES failing. It is the NES being exactly what its buffer arithmetic says it is, while software decides which eight sprites get to be real on any given line. The interesting engineering is all in that decision — and the documented evidence for how any specific game made it is thinner than the folklore suggests.

Questions readers actually ask

Why do some sprites disappear entirely instead of flickering?

Because the game never reorders its sprite list. If the same sprites occupy the first eight OAM slots on a crowded line every frame, the ones behind them are dropped every frame. Flicker requires the order to change between frames.

Does rotating the sprite list let more sprites appear?

No. Eight is the buffer capacity, and rotation does not change it. Rotation changes which sprites are drawn on which frames, not how many are drawn per frame.

Can I use the sprite overflow flag to detect when I have too many sprites on a line?

Not reliably. NESdev documents the flag’s detection logic as producing both false positives and false negatives, so it cannot be trusted as a limit indicator.

Is the eight-sprite limit the same as the 64-sprite OAM limit?

No. OAM holds 64 sprites for the whole frame. Secondary OAM holds 32 bytes, enough for eight sprites, and is what constrains each individual scanline. The two limits operate at different levels.