fix(physics): process sensors before collision response

Evaluate type-three captures from the pre-movement position, retain their missing radial rim candidates, and run type-four motion randomization before candidate resolution and position publication. Sensor groups now execute in binary ID order without Hold or Complete prematurely aborting later records.

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:
2026-08-23 18:37:03 +02:00
parent 95b6994735
commit 89880e9b45
4 changed files with 333 additions and 72 deletions
+63
View File
@@ -452,6 +452,48 @@ pub fn circle_collision_candidate(
})
}
/// Detect the non-capture boundary response of a type-3 record.
pub fn capture_collision_candidate(
old_position: MilliVec,
velocity: MilliVec,
center: Vec2,
radius: f32,
material: CollisionMaterial,
) -> Option<StaticCollisionCandidate> {
let center = MilliVec::from_position(center);
let radius_milli = (radius * 1_000.0).round() as i32;
let delta = MilliVec {
x: center.x.wrapping_sub(old_position.x),
y: center
.y
.wrapping_sub(old_position.y)
.wrapping_sub(2_000),
};
let surface_distance = milli_distance(delta).wrapping_sub(radius_milli);
let normal_x = Real48::from_i32(center.y.wrapping_sub(old_position.y));
let normal_y = Real48::from_i32(old_position.x.wrapping_sub(center.x));
let length = milli_distance(MilliVec {
x: normal_x.round_i32(),
y: normal_y.round_i32(),
});
if length == 0 {
return None;
}
let normal_velocity = normal_velocity(velocity, normal_x, normal_y, length);
if normal_velocity >= 0
|| normal_velocity.wrapping_abs() < surface_distance.wrapping_abs()
{
return None;
}
Some(StaticCollisionCandidate {
surface_distance,
normal_x,
normal_y,
normal_velocity,
material,
})
}
#[cfg(test)]
pub fn circle_collision_response(
old_position: MilliVec,
@@ -622,6 +664,27 @@ mod tests {
assert_eq!(response.surface_distance, 2_000);
}
#[test]
fn type_three_boundary_builds_the_recovered_radial_candidate() {
let candidate = capture_collision_candidate(
MilliVec {
x: 100_000,
y: 100_000,
},
MilliVec { x: 1_000, y: 0 },
vec2(103.0, 102.0),
3.8,
CollisionMaterial::line(0.6, 0.0),
)
.expect("the contacted type-three rim must retain a candidate");
assert_eq!(candidate.surface_distance, -800);
assert_ne!(
candidate.resolve(MilliVec { x: 1_000, y: 0 }, Real48::ZERO).velocity,
MilliVec { x: 1_000, y: 0 }
);
}
#[test]
fn auxiliary_kick_uses_the_recovered_negative_speed_threshold() {
let material = CollisionMaterial::line_with_kick(0.5, 0.1, -0.4, 0.4);