fix(physics): restore four-sided board bounds
Finish a ball before record scanning when its clamped prediction leaves x=(0,340000] or y=(0,460000], and remove only the escaping slot during multiball. Delete the invented post-move y>470 shortcut so collision responses that land outside persist until the original next-substep check. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - rumdl check CHANGELOG.md RECONSTRUCTION.md README.md - git diff --check
This commit is contained in:
+61
-7
@@ -243,6 +243,10 @@ fn retain_first_collision(
|
||||
}
|
||||
}
|
||||
|
||||
const fn predicted_outside_board(position: MilliVec) -> bool {
|
||||
position.x <= 0 || position.x > 340_000 || position.y <= 0 || position.y > 460_000
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct RuleState {
|
||||
wheel_holes: [bool; 5],
|
||||
@@ -703,6 +707,10 @@ impl Game {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
if predicted_outside_board(old_position.add(velocity)) {
|
||||
self.drain(events);
|
||||
return true;
|
||||
}
|
||||
let (best_static, scan, predicted) = self.scan_ordered_records_for_ball(
|
||||
&mut ball,
|
||||
1,
|
||||
@@ -824,12 +832,6 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
if self.ball.position.y > 470.0 {
|
||||
// Only malformed/out-of-table states reach this guard; the real
|
||||
// drain is collision object 2 at y=455.
|
||||
self.drain(events);
|
||||
return true;
|
||||
}
|
||||
collided
|
||||
}
|
||||
|
||||
@@ -1110,6 +1112,11 @@ impl Game {
|
||||
let mut velocity = MilliVec::from_velocity_per_second(ball.velocity);
|
||||
velocity.y += GRAVITY_MILLI_PER_STEP;
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
if predicted_outside_board(old_position.add(velocity)) {
|
||||
self.score_mode = ScoreMode::Normal;
|
||||
events.push(Event::Drain);
|
||||
return true;
|
||||
}
|
||||
let (best_static, scan, predicted) = self.scan_ordered_records_for_ball(
|
||||
&mut ball,
|
||||
2,
|
||||
@@ -1170,7 +1177,7 @@ impl Game {
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
ball.position = old_position.add(velocity).to_position();
|
||||
ball.velocity = velocity.to_velocity_per_second();
|
||||
if hit == Some((2, true)) || ball.position.y > 470.0 {
|
||||
if hit == Some((2, true)) {
|
||||
self.score_mode = ScoreMode::Normal;
|
||||
events.push(Event::Drain);
|
||||
return true;
|
||||
@@ -2690,6 +2697,53 @@ mod tests {
|
||||
assert!(game.ball.in_launcher);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_sided_prediction_bounds_finish_the_ball_before_record_scan() {
|
||||
for (position, velocity) in [
|
||||
(vec2(1.0, 200.0), MilliVec { x: -2_000, y: 0 }),
|
||||
(vec2(339.0, 200.0), MilliVec { x: 2_000, y: 0 }),
|
||||
(vec2(200.0, 1.0), MilliVec { x: 0, y: -2_000 }),
|
||||
(vec2(200.0, 459.0), MilliVec { x: 0, y: 2_000 }),
|
||||
] {
|
||||
let mut game = Game::new(1);
|
||||
game.object_active.fill(false);
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = position;
|
||||
game.ball.velocity = velocity.to_velocity_per_second();
|
||||
let mut events = Vec::new();
|
||||
|
||||
assert!(game.fixed_update(&mut events));
|
||||
|
||||
assert!(game.ball.in_launcher);
|
||||
assert_eq!(game.player().balls, 2);
|
||||
assert!(events.contains(&Event::Drain));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prediction_bounds_remove_only_the_escaping_multiball_slot() {
|
||||
let mut game = Game::new(1);
|
||||
game.object_active.fill(false);
|
||||
game.ball.in_launcher = false;
|
||||
game.ball.position = vec2(200.0, 200.0);
|
||||
let second = Ball {
|
||||
position: vec2(1.0, 200.0),
|
||||
velocity: MilliVec { x: -2_000, y: 0 }.to_velocity_per_second(),
|
||||
in_launcher: false,
|
||||
spin: Real48::ZERO,
|
||||
capture_age: 0,
|
||||
};
|
||||
game.secondary_ball = Some(second);
|
||||
game.score_mode = ScoreMode::Multiball;
|
||||
|
||||
assert!(game.advance_secondary_ball(&mut Vec::new()));
|
||||
|
||||
assert!(game.secondary_ball.is_none());
|
||||
assert_eq!(game.player().balls, 3);
|
||||
assert_eq!(game.score_mode, ScoreMode::Normal);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_turns_save_and_restore_all_rule_record_state() {
|
||||
let mut game = Game::new(2);
|
||||
|
||||
Reference in New Issue
Block a user