One thing I noticed about your code is you're making your own rectangle. The draw function returns a rect so you should pass that. It's useful because the rect it returns is constrained by the screen so there's less to update. Plus you don't have to make as many rects. But that doesn't fix your problem.
Here's just a list of observations I made while experimenting.
I'm not sure what's causing this issue. It's weird because it goes in all directions. It seems to be affected by the time it has to compute. Eg. if you change fps down to 20 it goes away. And if you change it to 120 it gets terrible. Also the amount of clipping is proportional to the speed at which you move. More speed = more clipping.
I'm not sure why flipping or increasing the size
of the rect fixes the problem but it does. If you inflate your rect
slightly it fixes it. But if you increase speed too much then it comes
back.
The final observation that I made is that's it's not possible to stop on a clipped rect. This makes me think this is a vsync issue. I'm pretty sure that pygame does not vsync unless it's in hardware accelerated fullscreen. So maybe the reason that flipping and bigger rects stop the issue is because they give the system a slight delay. I also managed to get two circles to draw and the effect goes away entirely. This might also have to do with a longer processing time.
Here's modified code that allows you to try to "catch" the clipping in action (it's not observable). It also toggles from inflating to not if you uncomment the right part. This doesn't have two circles.
import pygame
w,h=500,500
fps=40
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=w/2,h
dx = 10
r = pygame.Rect((0,0), (radius*2, radius*2))
r.center = (x, y)
inflate = False
def get_bbox(x,y):
ÂÂÂ left = x - radius
ÂÂÂ top = y - radius
ÂÂÂ width = radius * 2
ÂÂÂ height = radius * 2
ÂÂÂ return pygame.Rect((left, top), (width, height))
while True:
ÂÂÂ old_r=r
ÂÂÂ y-=dx
ÂÂÂ if y < 100:
ÂÂÂÂÂÂÂ y = w
ÂÂÂÂÂÂÂ #fps -= 5
ÂÂÂÂÂÂÂ #dx += 1
ÂÂÂÂÂÂÂ fps = 0
ÂÂÂÂÂÂÂ #inflate = not inflate
ÂÂÂ if fps <= 0:
ÂÂÂÂÂÂÂ fps = 40
ÂÂÂÂÂÂÂ pygame.time.wait(500)
ÂÂÂ screen.fill(pygame.Color("black"),old_r)
ÂÂÂ r = pygame.draw.circle(screen, color, (x, h-y), radius, 0)
ÂÂÂ if inflate: r.inflate_ip(20,20)
ÂÂÂ pygame.display.update([r,old_r])
ÂÂÂ clock.tick(fps)
I'm really at a loss as to what this actually is,
Jeffrey