Quick answer: A plain pygame.sprite.Group has no guaranteed draw order. Use pygame.sprite.LayeredUpdates and set each sprite’s _layer for deterministic stacking.
A newly-spawned effect sometimes draws under the player, sometimes over — the plain Group draws in whatever internal order it happens to have.
Group Order Is Unspecified
Group.draw() iterates the group’s internal storage. Add order roughly correlates but isn’t a contract — and removing/re-adding sprites scrambles it. Don’t rely on it for layering.
Use LayeredUpdates
group = pygame.sprite.LayeredUpdates()
# layer is read from sprite._layer when added,
# or pass it explicitly:
group.add(background, layer=0)
group.add(player, layer=10)
group.add(effect, layer=20)
LayeredUpdates.draw() renders strictly low layer to high. Spawn order no longer matters.
Change Layer at Runtime
group.change_layer(effect, 5) # move it behind the player
For dynamic z-changes (an item picked up, a character ducking behind cover), change_layer re-sorts that sprite immediately.
Within a Layer
Sprites sharing a layer still draw in add order among themselves. For per-sprite ordering inside a layer (top-down y-sort), give each a unique fractional layer or use a sort key — e.g. layer = int(sprite.rect.bottom).
Verifying
Spawn effects in any order — they always draw at their assigned layer. Moving a sprite’s layer re-stacks it immediately.
“Plain Group order is luck. LayeredUpdates plus explicit layers makes draw order data, not accident.”
For top-down games, set layer from the sprite’s y each frame — cheap, and you get correct depth sorting for free.