fix(physics): restore recovered flipper states

Replace the reversed oversized flipper vectors with the original binary rest
and raised geometry. Render the exact resource 997 flipper rectangles while
pressed and keep the live collision capsules aligned with those sprites.

Test Plan:
- cargo fmt --check
- cargo test
- cargo clippy --all-targets --all-features -- -D warnings
- compare forced raised-state rendering with the extracted resource rectangles
This commit is contained in:
2026-08-22 18:15:38 +02:00
parent bb53f88fb3
commit 4ef563361b
2 changed files with 109 additions and 36 deletions
+24
View File
@@ -452,6 +452,30 @@ impl App {
}, },
); );
} }
for (raised, region) in [
(
game.flippers.left_raised,
Rect::new(93.0, 373.0, 47.0, 52.0),
),
(
game.flippers.right_raised,
Rect::new(175.0, 372.0, 45.0, 53.0),
),
] {
if raised {
draw_texture_ex(
&self.assets.active_table,
region.x,
region.y,
WHITE,
DrawTextureParams {
dest_size: Some(vec2(region.w, region.h)),
source: Some(region),
..Default::default()
},
);
}
}
draw_texture( draw_texture(
&self.assets.ball, &self.assets.ball,
game.ball.position.x - 8.0, game.ball.position.x - 8.0,
+85 -36
View File
@@ -10,6 +10,12 @@ const FLIPPER_CONTACT_RADIUS: f32 = 9.0;
// the rendered ball radius to every recovered boundary. // the rendered ball radius to every recovered boundary.
const TABLE_LINE_RADIUS: f32 = 1.0; const TABLE_LINE_RADIUS: f32 = 1.0;
const SHOOTER_EXIT_GATE_ID: u8 = 22; const SHOOTER_EXIT_GATE_ID: u8 = 22;
const LEFT_FLIPPER_PIVOT: Vec2 = Vec2::new(103.0, 397.0);
const LEFT_FLIPPER_REST_TIP: Vec2 = Vec2::new(134.0, 419.0);
const LEFT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(134.0, 376.0);
const RIGHT_FLIPPER_PIVOT: Vec2 = Vec2::new(210.0, 397.0);
const RIGHT_FLIPPER_REST_TIP: Vec2 = Vec2::new(179.0, 419.0);
const RIGHT_FLIPPER_RAISED_TIP: Vec2 = Vec2::new(179.0, 376.0);
const GRAVITY: f32 = 135.0; const GRAVITY: f32 = 135.0;
const MAX_SPEED: f32 = 430.0; const MAX_SPEED: f32 = 430.0;
const BALL_SEARCH_DELAY: f32 = 3.0; const BALL_SEARCH_DELAY: f32 = 3.0;
@@ -45,6 +51,12 @@ pub enum Event {
Drain, Drain,
} }
#[derive(Clone, Copy, Debug, Default)]
pub struct Flippers {
pub left_raised: bool,
pub right_raised: bool,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Player { pub struct Player {
pub score: u32, pub score: u32,
@@ -97,8 +109,7 @@ pub struct Game {
pub lock_lights: u8, pub lock_lights: u8,
pub magnets: f32, pub magnets: f32,
pub tilted: bool, pub tilted: bool,
pub left_flipper_angle: f32, pub flippers: Flippers,
pub right_flipper_angle: f32,
pub bumper_flash: [f32; 3], pub bumper_flash: [f32; 3],
pub wheel_animation: f32, pub wheel_animation: f32,
pub robot_animation: f32, pub robot_animation: f32,
@@ -127,8 +138,7 @@ impl Game {
lock_lights: 0, lock_lights: 0,
magnets: 0.0, magnets: 0.0,
tilted: false, tilted: false,
left_flipper_angle: -2.72, flippers: Flippers::default(),
right_flipper_angle: -0.42,
bumper_flash: [0.0; 3], bumper_flash: [0.0; 3],
wheel_animation: 0.0, wheel_animation: 0.0,
robot_animation: 0.0, robot_animation: 0.0,
@@ -163,26 +173,11 @@ impl Game {
return Vec::new(); return Vec::new();
} }
let mut events = Vec::new(); let mut events = Vec::new();
let desired_left = if controls.left_flipper && !self.tilted { let old_flippers = self.flippers;
-2.18 self.flippers.left_raised = controls.left_flipper && !self.tilted;
} else { self.flippers.right_raised = controls.right_flipper && !self.tilted;
-2.72 if (self.flippers.left_raised && !old_flippers.left_raised)
}; || (self.flippers.right_raised && !old_flippers.right_raised)
let desired_right = if controls.right_flipper && !self.tilted {
-0.96
} else {
-0.42
};
let flipper_rate = 14.0 * frame_time;
let old_left = self.left_flipper_angle;
let old_right = self.right_flipper_angle;
self.left_flipper_angle +=
(desired_left - self.left_flipper_angle).clamp(-flipper_rate, flipper_rate);
self.right_flipper_angle +=
(desired_right - self.right_flipper_angle).clamp(-flipper_rate, flipper_rate);
if !self.tilted
&& ((controls.left_flipper && old_left < -2.68)
|| (controls.right_flipper && old_right > -0.46))
{ {
events.push(Event::Flipper); events.push(Event::Flipper);
} }
@@ -264,6 +259,11 @@ impl Game {
let mut drained = false; let mut drained = false;
for wall in WALLS { for wall in WALLS {
// Objects 66/68 and 81/83 are the resting flipper edges.
// Their live shapes are handled as moving capsules below.
if matches!(wall.id, 66 | 68 | 81 | 83) {
continue;
}
// The upper shooter exit is a one-way gate. After the outer // The upper shooter exit is a one-way gate. After the outer
// curve turns the launched ball down and left, object 22 must // curve turns the launched ball down and left, object 22 must
// let it enter the playfield. A ball approaching from below // let it enter the playfield. A ball approaching from below
@@ -308,6 +308,11 @@ impl Game {
for circle in PASSIVE_CIRCLES { for circle in PASSIVE_CIRCLES {
debug_assert_ne!(circle.id, 174, "the live ball is not a static obstacle"); debug_assert_ne!(circle.id, 174, "the live ball is not a static obstacle");
// Objects 67 and 82 are the movable flipper tips. The fixed
// pivot circles (65 and 80) remain valid in either position.
if matches!(circle.id, 67 | 82) {
continue;
}
circle_collision( circle_collision(
&mut self.ball.position, &mut self.ball.position,
&mut self.ball.velocity, &mut self.ball.velocity,
@@ -318,25 +323,24 @@ impl Game {
); );
} }
let left_flipper = flipper_segment(vec2(103.0, 397.0), self.left_flipper_angle); let (left_flipper, right_flipper) = self.flipper_segments();
let right_flipper = flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle);
if segment_collision( if segment_collision(
&mut self.ball.position, &mut self.ball.position,
&mut self.ball.velocity, &mut self.ball.velocity,
FLIPPER_CONTACT_RADIUS, FLIPPER_CONTACT_RADIUS,
left_flipper, left_flipper,
) && self.left_flipper_angle > -2.55 ) && self.flippers.left_raised
{ {
self.ball.velocity += vec2(-20.0, -115.0); self.ball.velocity += vec2(20.0, -115.0);
} }
if segment_collision( if segment_collision(
&mut self.ball.position, &mut self.ball.position,
&mut self.ball.velocity, &mut self.ball.velocity,
FLIPPER_CONTACT_RADIUS, FLIPPER_CONTACT_RADIUS,
right_flipper, right_flipper,
) && self.right_flipper_angle < -0.58 ) && self.flippers.right_raised
{ {
self.ball.velocity += vec2(20.0, -115.0); self.ball.velocity += vec2(-20.0, -115.0);
} }
for (index, bumper) in BUMPERS.into_iter().enumerate() { for (index, bumper) in BUMPERS.into_iter().enumerate() {
@@ -560,10 +564,27 @@ impl Game {
} }
self.finished = true; self.finished = true;
} }
fn flipper_segments(&self) -> (Segment, Segment) {
let left_tip = if self.flippers.left_raised {
LEFT_FLIPPER_RAISED_TIP
} else {
LEFT_FLIPPER_REST_TIP
};
let right_tip = if self.flippers.right_raised {
RIGHT_FLIPPER_RAISED_TIP
} else {
RIGHT_FLIPPER_REST_TIP
};
(
flipper_segment(LEFT_FLIPPER_PIVOT, left_tip),
flipper_segment(RIGHT_FLIPPER_PIVOT, right_tip),
)
}
} }
fn flipper_segment(pivot: Vec2, angle: f32) -> Segment { fn flipper_segment(pivot: Vec2, tip: Vec2) -> Segment {
Segment::new(pivot, pivot + vec2(angle.cos(), angle.sin()) * 49.0, 0.88) Segment::new(pivot, tip, 0.88)
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
@@ -751,8 +772,8 @@ mod tests {
assert!(game.tilted); assert!(game.tilted);
assert_eq!(game.bonus, 0); assert_eq!(game.bonus, 0);
game.left_flipper_angle = -2.18; game.flippers.left_raised = true;
game.right_flipper_angle = -0.96; game.flippers.right_raised = true;
let score = game.player().score; let score = game.player().score;
game.ball.in_launcher = false; game.ball.in_launcher = false;
game.ball.position = vec2(201.0, 285.0); game.ball.position = vec2(201.0, 285.0);
@@ -767,11 +788,39 @@ mod tests {
}, },
); );
assert!(game.left_flipper_angle < -2.18); assert!(!game.flippers.left_raised);
assert!(game.right_flipper_angle > -0.96); assert!(!game.flippers.right_raised);
assert_eq!(game.player().score, score); assert_eq!(game.player().score, score);
} }
#[test]
fn flippers_use_the_recovered_binary_positions() {
let mut game = Game::new(1);
let (left, right) = game.flipper_segments();
assert_eq!(
(left.start, left.end),
(LEFT_FLIPPER_PIVOT, LEFT_FLIPPER_REST_TIP)
);
assert_eq!(
(right.start, right.end),
(RIGHT_FLIPPER_PIVOT, RIGHT_FLIPPER_REST_TIP)
);
let events = game.update(
1.0 / 60.0,
3,
Controls {
left_flipper: true,
right_flipper: true,
..Controls::default()
},
);
let (left, right) = game.flipper_segments();
assert!(events.contains(&Event::Flipper));
assert_eq!(left.end, LEFT_FLIPPER_RAISED_TIP);
assert_eq!(right.end, RIGHT_FLIPPER_RAISED_TIP);
}
#[test] #[test]
fn drain_clears_latched_tilt() { fn drain_clears_latched_tilt() {
let mut game = Game::new(1); let mut game = Game::new(1);