fix(physics): restore the one-way shooter exit
Use the recovered rail rebound coefficient and let a launched ball cross object 22 in the exit direction after the upper curve. Strengthen the launch regression so a momentary lane crossing cannot hide a later rebound. Test Plan: - cargo fmt --check - cargo test - cargo clippy --all-targets --all-features -- -D warnings - verify the charged launch trajectory at detail levels 1 through 5
This commit is contained in:
+36
-16
@@ -9,6 +9,7 @@ const FLIPPER_CONTACT_RADIUS: f32 = 9.0;
|
||||
// geometry. A small contact epsilon preserves thin-line hits without adding
|
||||
// the rendered ball radius to every recovered boundary.
|
||||
const TABLE_LINE_RADIUS: f32 = 1.0;
|
||||
const SHOOTER_EXIT_GATE_ID: u8 = 22;
|
||||
const GRAVITY: f32 = 135.0;
|
||||
const MAX_SPEED: f32 = 430.0;
|
||||
const BALL_SEARCH_DELAY: f32 = 3.0;
|
||||
@@ -263,6 +264,18 @@ impl Game {
|
||||
|
||||
let mut drained = false;
|
||||
for wall in WALLS {
|
||||
// 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
|
||||
// still collides with the gate.
|
||||
if wall.id == SHOOTER_EXIT_GATE_ID
|
||||
&& self.ball.velocity.x < 0.0
|
||||
&& self.ball.velocity.y > 0.0
|
||||
&& self.ball.position.x < 290.0
|
||||
&& self.ball.position.y < 35.0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Object 25 is the one-way shooter stop. It catches a returning
|
||||
// ball, but must not reflect an upward launch from the line.
|
||||
if wall.id == 25 && self.ball.velocity.y < 0.0 {
|
||||
@@ -623,25 +636,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn charged_ball_clears_the_shooter_lane() {
|
||||
let mut game = Game::new(1);
|
||||
launch_ball(&mut game, 33);
|
||||
let mut entered_table = false;
|
||||
let mut minimum_y = game.ball.position.y;
|
||||
for detail in 1..=5 {
|
||||
let mut game = Game::new(1);
|
||||
launch_ball(&mut game, 33);
|
||||
let mut entered_table = false;
|
||||
let mut returned_to_launcher = false;
|
||||
let mut minimum_y = game.ball.position.y;
|
||||
|
||||
for _ in 0..240 {
|
||||
game.update(1.0 / 120.0, 5, Controls::default());
|
||||
minimum_y = minimum_y.min(game.ball.position.y);
|
||||
if game.ball.position.x < 298.0 {
|
||||
entered_table = true;
|
||||
break;
|
||||
for _ in 0..240 {
|
||||
game.update(1.0 / 120.0, detail, Controls::default());
|
||||
minimum_y = minimum_y.min(game.ball.position.y);
|
||||
if game.ball.position.x < 280.0 && game.ball.position.y > 30.0 {
|
||||
entered_table = true;
|
||||
}
|
||||
returned_to_launcher |= game.ball.in_launcher;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
entered_table,
|
||||
"the shooter curve should guide the ball onto the table; position={:?}, velocity={:?}, minimum_y={minimum_y}",
|
||||
game.ball.position, game.ball.velocity
|
||||
);
|
||||
assert!(
|
||||
entered_table,
|
||||
"detail {detail}: the shooter curve should guide the ball onto the table; position={:?}, velocity={:?}, minimum_y={minimum_y}",
|
||||
game.ball.position, game.ball.velocity
|
||||
);
|
||||
assert!(
|
||||
!returned_to_launcher,
|
||||
"detail {detail}: a launched ball must not rebound into the launcher"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
use crate::geometry::Segment;
|
||||
use macroquad::prelude::Vec2;
|
||||
|
||||
const WALL_BOUNCE: f32 = 0.82;
|
||||
// The ordinary rail registrations in `FUN_1000_34ab` use the recovered 0.1
|
||||
// rebound coefficient. The previous 0.82 value made the shooter curve behave
|
||||
// like a rubber bumper and sent the ball back down the launch lane.
|
||||
const WALL_BOUNCE: f32 = 0.10;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TableSegment {
|
||||
|
||||
Reference in New Issue
Block a user