fix(physics): restore fixed-point circle responses
Replace the overlap-and-separate circle approximation with the original swept ball-center entry test and radial/tangent fixed-point response. Preserve every type-1 record's Real48 rebound and tangent coefficients, plus the bumper normal kick derived from the 3800-unit speed scalar. Unify line and circle traversal in original object-id order and remove the old floating passive-circle and bumper impulses. Add exact tests for live object 155 and bumper 51 transitions, and keep moving flippers as the explicit remaining collision approximation. Test Plan: - `cargo test --all-targets` -- 44 passed - `cargo clippy --all-targets -- -D warnings` -- passed - `cargo build --profile production` -- passed - 20-second deterministic launch traversed bumpers and drained without rescue - live object 155 and bumper 51 fixed-point transitions matched exactly - `git diff --cached --check` -- passed
This commit is contained in:
+73
-71
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
geometry::{Segment, circle_collision, closest_point, segment_collision},
|
||||
geometry::{Segment, closest_point, segment_collision},
|
||||
original_physics::{
|
||||
GRAVITY_MILLI_PER_STEP, MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, STEP_SECONDS,
|
||||
collide_with_line,
|
||||
collide_with_circle, collide_with_line,
|
||||
},
|
||||
table::{BUMPERS, PASSIVE_CIRCLES, WALLS},
|
||||
};
|
||||
@@ -406,28 +406,57 @@ impl Game {
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
let mut position = old_position.add(velocity);
|
||||
let mut hit_wall = None;
|
||||
for wall in WALLS {
|
||||
if self.claw.active && (12..=20).contains(&wall.id) {
|
||||
let mut hit_circle = None;
|
||||
for object_id in 1..=175 {
|
||||
if self.claw.active && (12..=20).contains(&object_id) {
|
||||
continue;
|
||||
}
|
||||
// The original moves these four records with the flipper bodies.
|
||||
if matches!(wall.id, 66 | 68 | 81 | 83) {
|
||||
continue;
|
||||
if let Some(wall) = WALLS.iter().find(|wall| wall.id == object_id) {
|
||||
// The original moves these four records with the flipper bodies.
|
||||
if matches!(wall.id, 66 | 68 | 81 | 83) {
|
||||
continue;
|
||||
}
|
||||
let mut response = velocity;
|
||||
if collide_with_line(
|
||||
old_position,
|
||||
&mut response,
|
||||
wall.segment.start,
|
||||
wall.segment.end,
|
||||
f64::from(wall.normal_rebound),
|
||||
f64::from(wall.tangent_coupling),
|
||||
) {
|
||||
velocity = response;
|
||||
position = old_position.add(velocity);
|
||||
hit_wall = Some(wall.id);
|
||||
self.last_collision_id = Some(wall.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut response = velocity;
|
||||
if collide_with_line(
|
||||
old_position,
|
||||
&mut response,
|
||||
wall.segment.start,
|
||||
wall.segment.end,
|
||||
f64::from(wall.normal_rebound),
|
||||
f64::from(wall.tangent_coupling),
|
||||
) {
|
||||
velocity = response;
|
||||
position = old_position.add(velocity);
|
||||
hit_wall = Some(wall.id);
|
||||
self.last_collision_id = Some(wall.id);
|
||||
break;
|
||||
let circle = PASSIVE_CIRCLES
|
||||
.iter()
|
||||
.chain(BUMPERS.iter())
|
||||
.find(|circle| circle.id == object_id);
|
||||
if let Some(circle) = circle {
|
||||
// Objects 67 and 82 are the movable flipper tips.
|
||||
if matches!(circle.id, 67 | 82) {
|
||||
continue;
|
||||
}
|
||||
let mut response = velocity;
|
||||
if collide_with_circle(
|
||||
old_position,
|
||||
&mut response,
|
||||
circle.center,
|
||||
circle.contact_radius,
|
||||
f64::from(circle.normal_rebound),
|
||||
f64::from(circle.tangent_coupling),
|
||||
f64::from(circle.normal_kick),
|
||||
) {
|
||||
velocity = response;
|
||||
position = old_position.add(velocity);
|
||||
hit_circle = Some(circle.id);
|
||||
self.last_collision_id = Some(circle.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ball.position = position.to_position();
|
||||
@@ -456,64 +485,37 @@ 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;
|
||||
}
|
||||
if circle_collision(
|
||||
if hit_wall.is_none() && hit_circle.is_none() {
|
||||
let (left_flipper, right_flipper) = self.flipper_segments();
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
0.0,
|
||||
circle.center,
|
||||
circle.contact_radius,
|
||||
0.0,
|
||||
FLIPPER_CONTACT_RADIUS,
|
||||
left_flipper,
|
||||
) {
|
||||
self.last_collision_id = Some(circle.id);
|
||||
}
|
||||
}
|
||||
|
||||
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.last_collision_id = Some(66);
|
||||
}
|
||||
if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
FLIPPER_CONTACT_RADIUS,
|
||||
right_flipper,
|
||||
) {
|
||||
self.last_collision_id = Some(81);
|
||||
}
|
||||
|
||||
for (index, bumper) in BUMPERS.into_iter().enumerate() {
|
||||
let hit = circle_collision(
|
||||
self.last_collision_id = Some(66);
|
||||
} else if segment_collision(
|
||||
&mut self.ball.position,
|
||||
&mut self.ball.velocity,
|
||||
0.0,
|
||||
bumper.center,
|
||||
bumper.contact_radius,
|
||||
105.0,
|
||||
);
|
||||
if hit {
|
||||
self.last_collision_id = Some(bumper.id);
|
||||
}
|
||||
if hit && !self.tilted && self.bumper_cooldown <= 0.0 {
|
||||
self.add_score(self.player().bumper_value);
|
||||
self.bonus = self.bonus.saturating_add(100);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
events.push(Event::Bumper);
|
||||
FLIPPER_CONTACT_RADIUS,
|
||||
right_flipper,
|
||||
) {
|
||||
self.last_collision_id = Some(81);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = hit_circle
|
||||
.and_then(|circle_id| BUMPERS.iter().position(|bumper| bumper.id == circle_id))
|
||||
&& !self.tilted
|
||||
&& self.bumper_cooldown <= 0.0
|
||||
{
|
||||
self.add_score(self.player().bumper_value);
|
||||
self.bonus = self.bonus.saturating_add(100);
|
||||
self.bumper_cooldown = 0.08;
|
||||
self.bumper_flash[index] = 0.16;
|
||||
events.push(Event::Bumper);
|
||||
}
|
||||
|
||||
if !self.tilted {
|
||||
self.check_targets(events);
|
||||
self.check_media(events);
|
||||
|
||||
Reference in New Issue
Block a user