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:
+29
-16
@@ -54,14 +54,27 @@ pub struct StaticCircle {
|
||||
pub id: u8,
|
||||
pub center: Vec2,
|
||||
pub contact_radius: f32,
|
||||
pub normal_rebound: f32,
|
||||
pub tangent_coupling: f32,
|
||||
pub normal_kick: f32,
|
||||
}
|
||||
|
||||
impl StaticCircle {
|
||||
const fn new(id: u8, center: Vec2, contact_radius: f32) -> Self {
|
||||
let (normal_rebound, tangent_coupling, normal_kick) = match id {
|
||||
54 | 56 | 58 | 65 | 69 | 71 | 73 => (0.50, 0.10, 0.0),
|
||||
67 | 80 | 82 => (0.50, 0.20, 0.0),
|
||||
167 | 172 => (0.60, 0.0, 0.0),
|
||||
51..=53 => (0.80, 0.10, 0.40),
|
||||
_ => (0.60, 0.10, 0.0),
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
center,
|
||||
contact_radius,
|
||||
normal_rebound,
|
||||
tangent_coupling,
|
||||
normal_kick,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,10 +245,7 @@ pub const BUMPERS: [StaticCircle; 3] = [
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
geometry::circle_collision,
|
||||
original_physics::{MilliVec, collide_with_line},
|
||||
};
|
||||
use crate::original_physics::{MilliVec, collide_with_circle, collide_with_line};
|
||||
|
||||
#[test]
|
||||
fn recovered_object_inventory_is_complete() {
|
||||
@@ -302,28 +312,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_recovered_circle_separates_and_reflects_the_ball() {
|
||||
fn every_recovered_circle_uses_its_fixed_point_response() {
|
||||
for circle in PASSIVE_CIRCLES.iter().chain(BUMPERS.iter()) {
|
||||
let mut position = circle.center + Vec2::new(circle.contact_radius - 0.5, 0.0);
|
||||
let mut velocity = Vec2::new(-430.0, 0.0);
|
||||
let hit = circle_collision(
|
||||
&mut position,
|
||||
let old_position = MilliVec::from_position(
|
||||
circle.center + Vec2::new(circle.contact_radius + 1.0, 0.0),
|
||||
);
|
||||
let mut velocity = MilliVec { x: -2_000, y: 0 };
|
||||
let hit = collide_with_circle(
|
||||
old_position,
|
||||
&mut velocity,
|
||||
0.0,
|
||||
circle.center,
|
||||
circle.contact_radius,
|
||||
0.0,
|
||||
f64::from(circle.normal_rebound),
|
||||
f64::from(circle.tangent_coupling),
|
||||
f64::from(circle.normal_kick),
|
||||
);
|
||||
|
||||
assert!(hit, "object {} missed an overlapping ball", circle.id);
|
||||
assert!(hit, "object {} missed an entering path", circle.id);
|
||||
assert!(
|
||||
position.x >= circle.center.x + circle.contact_radius,
|
||||
"object {} did not separate the ball",
|
||||
velocity.x > 0,
|
||||
"object {} did not reflect inward velocity",
|
||||
circle.id
|
||||
);
|
||||
assert!(
|
||||
velocity.x > 0.0,
|
||||
"object {} did not reflect inward velocity",
|
||||
old_position.add(velocity).x > old_position.x,
|
||||
"object {} did not advance away from its surface",
|
||||
circle.id
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user