Planning your SNES game budgets space — the VRAM, CGRAM and OAM your scene fills (and make budget measures it). This guide budgets time: what one frame can actually do at 60 Hz before the game stutters. Space is what fits on the cartridge; time is what fits in 1/60th of a second. Both are fixed, and both are design levers.
Every 1/60 s (NTSC) the screen draws top to bottom, then blanks briefly before the next one:
The single most common frame-timing bug follows directly: do your heavy computation first, during active display, and fire the VRAM upload the instant VBlank starts. Compute after WaitForVBlank() and the upload spills past VBlank's end — the PPU drops it, and you get garbage or a dropped update. (mode2 — offset-per-tile: per-column scroll from BG3 hit exactly this; its loop builds the offset table during active display and DMAs it at VBlank start.)
DMA moves roughly 1 byte per fast cycle, and VBlank is only ~38 scanlines, so the hard ceiling is about 6 KB per VBlank — but once the NMI handler has read the controllers and done its own housekeeping, **~4 KB is the safe working budget** you should plan around.
That shapes real decisions:
Warning, from experience. A "black flash at the top of the screen" on an update almost always means a force-blank upload that overran VBlank into the first scanlines. Move the write into VBlank, shrink it under the budget, or commit to a full force-blank frame — don't leave it straddling the boundary.
Separate from how many sprites exist (128) is how many the PPU can draw on one scanline: 32 sprites, and 34 8×8 tile-slivers, per line. Cross either and sprites drop out — culled from the lowest OAM priority first, and an off-screen-left sprite still counts (a documented quirk). This is a render limit, distinct from the CPU cost of updating many sprites.
The levers: spread action vertically so the busy lines are few; keep large sprites sparse; and remember that updating 128 sprites' positions in C is its own cost — sprite_swarm — a bouncing swarm, and the OAM throughput ceiling measures where per-sprite C motion runs out of frame time (around three dozen sprites), which is why heavy sprite math often moves off the main CPU.
The 65816 runs at 3.58 MHz (FastROM) or 2.68 MHz (slow), so a frame is only so many thousand cycles. When logic outgrows it, in rough order of reach-for:
The failure is graceful, not a crash: lag frames. Miss a VBlank and the game simply runs at 30 fps (or 20, or 15) instead of 60 — the classic 16-bit "slowdown." That is a legitimate design outcome, not only a bug: plenty of beloved SNES games drop to 30 fps in their busiest moments on purpose, and designing for 30 fps buys you double the CPU and DMA per update. Decide your target framerate up front and budget to it, rather than discovering it in the explosion that fills the screen.
Space is measured with make budget; time is measured in frames. For both, plan the busiest moment, not the average — the scene with the most sprites, the biggest transfer, the heaviest logic. If the worst case fits the frame, the rest is comfortable.
Sources: fullsnes · Anomie's SNES timing doc · SNESdev Wiki — DMA / VBlank · Bumbershoot Software (DMA budget & FastROM).