fix(physics): apply predicted record broadphase
Use unresolved type-one/type-two candidates in the production scan and gate detection on the original predicted-position bounds with the registered five-pixel ball margin. This restores the 1000:9b69 broadphase, including overshoot rejection, while keeping late response resolution ready for ordered type-three/type-four integration. 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:
+54
-20
@@ -4,8 +4,9 @@ use crate::{
|
||||
geometry::Segment,
|
||||
original_physics::{
|
||||
CollisionMaterial, CollisionResponse, GRAVITY_MILLI_PER_STEP,
|
||||
MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, STEP_SECONDS, ball_collision_response,
|
||||
circle_collision_response, line_collision_response, milli_distance, path_intersects_circle,
|
||||
MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, STEP_SECONDS, StaticCollisionCandidate,
|
||||
ball_collision_response, circle_collision_candidate, line_collision_candidate,
|
||||
milli_distance, path_intersects_circle,
|
||||
},
|
||||
real48::Real48,
|
||||
table::{
|
||||
@@ -672,7 +673,10 @@ impl Game {
|
||||
self.apply_magnetic_fields(old_position, &mut velocity);
|
||||
let movement_velocity = velocity;
|
||||
let mut position = old_position.add(velocity);
|
||||
let mut best_collision = self.find_static_collision(old_position, velocity, self.ball.spin);
|
||||
let best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
let mut best_collision = best_static.map(|(id, is_wall, candidate)| {
|
||||
(id, is_wall, candidate.resolve(velocity, self.ball.spin))
|
||||
});
|
||||
let mut transferred_secondary_velocity = None;
|
||||
if let Some(secondary) = self.secondary_ball {
|
||||
let response = ball_collision_response(
|
||||
@@ -774,14 +778,14 @@ impl Game {
|
||||
collided
|
||||
}
|
||||
|
||||
fn find_static_collision(
|
||||
fn find_static_collision_candidate(
|
||||
&self,
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
spin: Real48,
|
||||
) -> Option<(u8, bool, CollisionResponse)> {
|
||||
) -> Option<(u8, bool, StaticCollisionCandidate)> {
|
||||
let mut best = None;
|
||||
let layer = if old_position.y < 250_000 { 0x01 } else { 0x02 };
|
||||
let predicted = old_position.add(velocity);
|
||||
for object_id in 1..=175 {
|
||||
if !self.object_active[usize::from(object_id)]
|
||||
|| self.claw.active && (12..=20).contains(&object_id)
|
||||
@@ -793,6 +797,15 @@ impl Game {
|
||||
continue;
|
||||
}
|
||||
let segment = self.live_wall_segment(wall.id, wall.segment);
|
||||
let start = MilliVec::from_position(segment.start);
|
||||
let end = MilliVec::from_position(segment.end);
|
||||
if predicted.x < start.x.min(end.x).wrapping_sub(5_000)
|
||||
|| predicted.x > start.x.max(end.x).wrapping_add(5_000)
|
||||
|| predicted.y < start.y.min(end.y).wrapping_sub(5_000)
|
||||
|| predicted.y > start.y.max(end.y).wrapping_add(5_000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let material = if self.tilted {
|
||||
CollisionMaterial::line(
|
||||
f64::from(wall.normal_rebound),
|
||||
@@ -806,17 +819,16 @@ impl Game {
|
||||
f64::from(wall.response_kick),
|
||||
)
|
||||
};
|
||||
if let Some(response) = line_collision_response(
|
||||
if let Some(candidate) = line_collision_candidate(
|
||||
old_position,
|
||||
velocity,
|
||||
segment.start,
|
||||
segment.end,
|
||||
material,
|
||||
spin,
|
||||
) && best.is_none_or(|(_, _, closest): (u8, bool, CollisionResponse)| {
|
||||
response.surface_distance <= closest.surface_distance
|
||||
) && best.is_none_or(|(_, _, closest): (u8, bool, StaticCollisionCandidate)| {
|
||||
candidate.surface_distance <= closest.surface_distance
|
||||
}) {
|
||||
best = Some((wall.id, true, response));
|
||||
best = Some((wall.id, true, candidate));
|
||||
}
|
||||
}
|
||||
let circle = PASSIVE_CIRCLES
|
||||
@@ -825,7 +837,18 @@ impl Game {
|
||||
.find(|circle| circle.id == object_id);
|
||||
if let Some(circle) = circle
|
||||
&& circle.layer_mask & layer != 0
|
||||
&& let Some(response) = circle_collision_response(
|
||||
&& {
|
||||
let center = MilliVec::from_position(
|
||||
self.live_circle_center(circle.id, circle.center),
|
||||
);
|
||||
let margin =
|
||||
MilliVec::from_position(vec2(circle.contact_radius + 5.0, 0.0)).x;
|
||||
predicted.x >= center.x.wrapping_sub(margin)
|
||||
&& predicted.x <= center.x.wrapping_add(margin)
|
||||
&& predicted.y >= center.y.wrapping_sub(margin)
|
||||
&& predicted.y <= center.y.wrapping_add(margin)
|
||||
}
|
||||
&& let Some(candidate) = circle_collision_candidate(
|
||||
old_position,
|
||||
velocity,
|
||||
self.live_circle_center(circle.id, circle.center),
|
||||
@@ -839,13 +862,12 @@ impl Game {
|
||||
f64::from(circle.normal_kick)
|
||||
},
|
||||
),
|
||||
spin,
|
||||
)
|
||||
&& best.is_none_or(|(_, _, closest)| {
|
||||
response.surface_distance <= closest.surface_distance
|
||||
candidate.surface_distance <= closest.surface_distance
|
||||
})
|
||||
{
|
||||
best = Some((circle.id, false, response));
|
||||
best = Some((circle.id, false, candidate));
|
||||
}
|
||||
}
|
||||
best
|
||||
@@ -869,7 +891,10 @@ impl Game {
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
self.apply_magnetic_fields(old_position, &mut velocity);
|
||||
let movement_velocity = velocity;
|
||||
let mut best_collision = self.find_static_collision(old_position, velocity, ball.spin);
|
||||
let best_static = self.find_static_collision_candidate(old_position, velocity);
|
||||
let mut best_collision = best_static.map(|(id, is_wall, candidate)| {
|
||||
(id, is_wall, candidate.resolve(velocity, ball.spin))
|
||||
});
|
||||
|
||||
let mut transferred_primary_velocity = None;
|
||||
if primary_slot_active
|
||||
@@ -2382,28 +2407,37 @@ mod tests {
|
||||
let velocity = MilliVec { x: 0, y: -15_000 };
|
||||
|
||||
assert_eq!(
|
||||
game.find_static_collision(
|
||||
game.find_static_collision_candidate(
|
||||
MilliVec {
|
||||
x: 25_000,
|
||||
y: 249_000,
|
||||
},
|
||||
velocity,
|
||||
Real48::ZERO,
|
||||
)
|
||||
.map(|collision| collision.0),
|
||||
Some(48)
|
||||
);
|
||||
assert!(
|
||||
game.find_static_collision(
|
||||
game.find_static_collision_candidate(
|
||||
MilliVec {
|
||||
x: 25_000,
|
||||
y: 250_000,
|
||||
},
|
||||
velocity,
|
||||
Real48::ZERO,
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
game.find_static_collision_candidate(
|
||||
MilliVec {
|
||||
x: 25_000,
|
||||
y: 249_000,
|
||||
},
|
||||
MilliVec { x: 0, y: -40_000 },
|
||||
)
|
||||
.is_none(),
|
||||
"the original predicted-position broadphase rejects an overshoot"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user