Repository navigation
Spikes Paddle Sprite Sizing
Decision note. Continues 2026-06-06-paddle-animation-collision-spike.md (the scaffold) and feeds the PlayerSprite dev panel.
paddle.gd::_apply_size() read paddle_size and drove two concerns from one number: the collider height (gameplay) and the sprite scale (visual, via _scale_sprite). Tuning the look fought the hitbox; the sprite could not size independently because sizing was gameplay-owned shared state. Items modified paddle_size as a stat (grip_tape grew it, wrist_brace shrank it), so the coupling reached into item balance too.
Two independent systems. The sprite never reads paddle_size; gameplay never reads sprite dimensions.
| Concern | Owner | Driven by | Persisted |
|---|---|---|---|
| Collider height |
paddle_size on PaddleConfig
|
fixed config value | yes (config) |
| Sprite width + height | the paddle's sprite | the PlayerSprite dev panel |
no (live tuning) |
-
Retire the size effect.
paddle_sizestops being a resolved stat.grip_tape(size-only effect) is deleted.wrist_bracekeeps its ball-speed buff, loses itspaddle_sizepenalty.paddle_size_minand the_resolvepath for size are removed; the collider readspaddle_sizedirectly as a fixed dimension. -
Cut the seam. Remove
_scale_sprite(new_size)from_apply_size(). The collider path and the sprite path no longer call each other. -
Sprite owns its size. The sprite gets independent width and height inputs, panel-driven, indexed on its own dimensions, never on
paddle_size.
The collider and the sprite are not kept in sync automatically. Art is drawn to read correctly around the hitbox; the panel's collider-visibility toggle exists to check they line up. Full independence, no default-derive leash.
The ball must interact with ONLY a small racket zone, not the whole character body. The standard 2D idiom (fighting-game hitbox/hurtbox, bullet-hell) is: the CharacterBody2D handles terrain, and separate child Area2D nodes on their own layers handle hit-detection. A child Area2D carries independent collision_layer/collision_mask (per-CollisionObject), which is the normal, supported pattern. (A child physics BODY shares the parent's layer; an Area2D does not. That is why the racket is an Area2D, not a body.)
Today the ball returns by pure physics restitution (bounce = 1.0) off the paddle body, and the existing paddle_return_angle_max_degrees / paddle_english_coefficient / bounce min-max stats shape that engine reflection. Arcade ball games (Breakout, Pong, Arkanoid) conventionally CODE-APPLY the paddle return instead, because the return angle should depend on the contact point and paddle motion, which is exactly what those stats encode. So:
-
Wall body. The
CharacterBody2D, on a layer the ball does NOT mask. The ball passes through the character body entirely.move_and_slideagainst walls only. -
Racket hitbox. A child
Area2D, racket-sized, mid-body, on its own layer, masking the ball. Detects the ball entering. Position and size PANEL-TUNABLE. -
Code-applied return (already exists).
effect_processor._apply_paddle_offset_returnALREADY setsball.linear_velocity = direction * ball.speedfrom contact offset, english, and the bounce clamps. Physics restitution is redundant; the code overwrites the velocity on every hit. So retiring restitution changes no feel: only the TRIGGER moves. The racket Area2D detecting the ball fires the sameon_ball_hit+process_hitpath. The ball passes through the body; the racket detection drives the existing return.half_height(the offset denominator) should reference the racket zone, not the old footprint collider.
The panel's show-collider toggle must DRAW the shapes in the running game. CollisionShape2D.visible does not draw at runtime (it is an editor property); use get_tree().debug_collisions_hint or a custom _draw. The toggle exists so both new colliders can be seen and aligned during tuning.
The PlayerSprite panel drives the placeholder sprite's size with sprite.scale.x/y. This violates feedback_no_node2d_scale (scale compounds coordinate-space, hit-rect, and physics math) and is deliberate temporary scaffolding, accepted only because the panel is a throwaway tuning instrument whose job is to find a target dimension by eye, then be discarded. The product is the dimension number, not the scaled placeholder.
Before the real paddle art lands, this must change to bounds-based sizing: the sprite is shown at its true size via SpriteFrames authored at the tuned dimensions, not by scaling the Node2D. Tracked as debt; do not let the scale path survive into shipped art.
Redesigning grip_tape (deleted, not replaced). Rebalancing wrist_brace beyond removing the size penalty. The SpriteFrames-swap pipeline that lands real art at the chosen dimensions.
Contributing
The game
The Cast
Process
Reference