Quick answer: vsync=1 only takes effect with the pygame.SCALED flag (or a hardware-accelerated surface). Pass pygame.SCALED alongside vsync=1.

A game passes vsync=1 to set_mode but the frame rate still runs unlimited and the screen tears. vsync was silently ignored.

vsync Needs SCALED

screen = pygame.display.set_mode(
    (1280, 720),
    pygame.SCALED,        # required for vsync to apply
    vsync=1)

On a plain software surface, the vsync request is dropped. pygame.SCALED routes through an accelerated path where vsync is honored.

Confirm It Worked

set_mode doesn’t raise if vsync can’t be enabled — it just falls back. Sanity-check by measuring frame rate: with vsync on it should sit at the monitor’s refresh rate (e.g. 60).

Still Call clock.tick

Even with vsync, call clock.tick(target_fps). vsync paces presentation; clock.tick gives you delta time and a fallback cap if vsync isn’t available on the user’s machine.

Fullscreen + SCALED

SCALED also handles resolution scaling and letterboxing in fullscreen — it pairs naturally with vsync for a clean, tear-free fullscreen mode.

Verifying

With SCALED + vsync, frame rate locks to the monitor refresh and tearing disappears. On a machine where vsync can’t engage, clock.tick still caps the rate.

“vsync rides on SCALED. Request both, and keep clock.tick as a fallback cap.”

SCALED is the modern default for most Pygame games — it gives you vsync, resolution independence, and clean fullscreen in one flag.