Quick answer: rect.clamp_ip(bounds) modifies rect, not bounds. The _ip suffix means “in place on self”. If you want a copy, use rect.clamp(bounds).
A player movement clamp uses world.clamp_ip(player). After play, the world rect has shrunk to the player’s size; the player flies off into nothing. The clamp ran on the wrong rect because _ip mutates self, not the argument.
The _ip Convention
Pygame Rect’s in-place methods all follow the same pattern: rect.METHOD_ip(args) modifies rect and returns None. Examples:
inflate_ip(dx, dy): rect grows/shrinks by dx,dy.move_ip(dx, dy): rect translates by dx,dy.clamp_ip(bounds): rect’s position is clamped to fit inside bounds.union_ip(other): rect becomes the union of self and other.
The non-_ip versions return new rects without mutation:
r.inflate(dx, dy)returns a new rect.r.move(dx, dy)returns a new rect.r.clamp(bounds)returns a new clamped rect.
The Right Way to Clamp Player to World
# player rect should stay inside world rect
player.rect.clamp_ip(world_rect)
# after: player.rect’s position is clamped, world_rect untouched
Call clamp_ip on the rect you want to change. The argument stays the same.
Common Inversion Pattern
Developers reading “clamp X to Y” sometimes write Y.clamp_ip(X) by mistake — intuitive English, wrong semantics. Mnemonic: in Pygame, verbs act on self. player.clamp_ip(world) — player does the clamping action; player is the one that changes.
Verifying
Print rects before and after the call. With player.clamp_ip(world), player’s position changes; world doesn’t. With world.clamp_ip(player) (the bug), world shrinks. Easy to spot once you log both.
“The _ip suffix means ‘in place on self’. The method acts on the receiver, not the argument. Always.”
When you read someone’s code calling rect_a.clamp_ip(rect_b), mentally rewrite it as “a is clamped to b” — b is the constraint, a is what changes.