From 4ef563361b01e7991fef35c82d446667d69c074e Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 22 Aug 2026 18:15:38 +0200 Subject: [PATCH] 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 --- tdkpin-rs/src/app.rs | 24 +++++++++ tdkpin-rs/src/game.rs | 121 +++++++++++++++++++++++++++++------------- 2 files changed, 109 insertions(+), 36 deletions(-) diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index dd76719..ef88215 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -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( &self.assets.ball, game.ball.position.x - 8.0, diff --git a/tdkpin-rs/src/game.rs b/tdkpin-rs/src/game.rs index f2c561f..289a7ec 100644 --- a/tdkpin-rs/src/game.rs +++ b/tdkpin-rs/src/game.rs @@ -10,6 +10,12 @@ const FLIPPER_CONTACT_RADIUS: f32 = 9.0; // the rendered ball radius to every recovered boundary. const TABLE_LINE_RADIUS: f32 = 1.0; 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 MAX_SPEED: f32 = 430.0; const BALL_SEARCH_DELAY: f32 = 3.0; @@ -45,6 +51,12 @@ pub enum Event { Drain, } +#[derive(Clone, Copy, Debug, Default)] +pub struct Flippers { + pub left_raised: bool, + pub right_raised: bool, +} + #[derive(Clone, Debug)] pub struct Player { pub score: u32, @@ -97,8 +109,7 @@ pub struct Game { pub lock_lights: u8, pub magnets: f32, pub tilted: bool, - pub left_flipper_angle: f32, - pub right_flipper_angle: f32, + pub flippers: Flippers, pub bumper_flash: [f32; 3], pub wheel_animation: f32, pub robot_animation: f32, @@ -127,8 +138,7 @@ impl Game { lock_lights: 0, magnets: 0.0, tilted: false, - left_flipper_angle: -2.72, - right_flipper_angle: -0.42, + flippers: Flippers::default(), bumper_flash: [0.0; 3], wheel_animation: 0.0, robot_animation: 0.0, @@ -163,26 +173,11 @@ impl Game { return Vec::new(); } let mut events = Vec::new(); - let desired_left = if controls.left_flipper && !self.tilted { - -2.18 - } else { - -2.72 - }; - 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)) + let old_flippers = self.flippers; + self.flippers.left_raised = controls.left_flipper && !self.tilted; + self.flippers.right_raised = controls.right_flipper && !self.tilted; + if (self.flippers.left_raised && !old_flippers.left_raised) + || (self.flippers.right_raised && !old_flippers.right_raised) { events.push(Event::Flipper); } @@ -264,6 +259,11 @@ impl Game { let mut drained = false; 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 // curve turns the launched ball down and left, object 22 must // let it enter the playfield. A ball approaching from below @@ -308,6 +308,11 @@ impl Game { for circle in PASSIVE_CIRCLES { 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( &mut self.ball.position, &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 right_flipper = flipper_segment(vec2(210.0, 397.0), self.right_flipper_angle); + let (left_flipper, right_flipper) = self.flipper_segments(); if segment_collision( &mut self.ball.position, &mut self.ball.velocity, FLIPPER_CONTACT_RADIUS, 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( &mut self.ball.position, &mut self.ball.velocity, FLIPPER_CONTACT_RADIUS, 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() { @@ -560,10 +564,27 @@ impl Game { } 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 { - Segment::new(pivot, pivot + vec2(angle.cos(), angle.sin()) * 49.0, 0.88) +fn flipper_segment(pivot: Vec2, tip: Vec2) -> Segment { + Segment::new(pivot, tip, 0.88) } #[allow(clippy::too_many_lines)] @@ -751,8 +772,8 @@ mod tests { assert!(game.tilted); assert_eq!(game.bonus, 0); - game.left_flipper_angle = -2.18; - game.right_flipper_angle = -0.96; + game.flippers.left_raised = true; + game.flippers.right_raised = true; let score = game.player().score; game.ball.in_launcher = false; game.ball.position = vec2(201.0, 285.0); @@ -767,11 +788,39 @@ mod tests { }, ); - assert!(game.left_flipper_angle < -2.18); - assert!(game.right_flipper_angle > -0.96); + assert!(!game.flippers.left_raised); + assert!(!game.flippers.right_raised); 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] fn drain_clears_latched_tilt() { let mut game = Game::new(1);