fix(physics): port the Borland Real48 collision core
Add a bit-exact six-byte Real48 implementation for integer conversion, rounding, comparison, add/subtract, multiply/divide, and Newton square root. Use the normalized Borland random-register result and route speed clamps, segment/circle detection, moving flippers, captures, triggers, and magnetic fields through the recovered arithmetic instead of host floating formulas. Preserve Real48 spin per ball and feed it through the original tangent/spin response, separating zero-spin C fixtures from retained-spin Wine traces. Replace path-progress selection with surface-distance ordering and implement dynamic records 174/175, including impulse transfer to the other slot, normal_velocity-1000 response, and the second post-collision speed clamp. Test Plan: - `cargo test --all-targets` -- passed, 69 tests - `cargo clippy --all-targets -- -D warnings` -- passed - `rumdl check tdkpin-rs/CHANGELOG.md tdkpin-rs/RECONSTRUCTION.md` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
//! Moving-flipper collision response reconstructed from `1000:7ed9`.
|
||||
|
||||
use crate::original_physics::MilliVec;
|
||||
use crate::{original_physics::MilliVec, real48::Real48};
|
||||
|
||||
const SEARCH_RADIUS: i32 = 54_000;
|
||||
const RESPONSE_RADIUS: f64 = 44_000.0;
|
||||
const ONE: Real48 = Real48::from_bytes([0x81, 0, 0, 0, 0, 0]);
|
||||
const TWO: Real48 = Real48::from_bytes([0x82, 0, 0, 0, 0, 0]);
|
||||
const TWO_FIFTHS: Real48 =
|
||||
Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]);
|
||||
const THOUSAND: Real48 = Real48::from_bytes([0x8a, 0, 0, 0, 0, 0x7a]);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum FlipperSide {
|
||||
@@ -85,15 +89,14 @@ fn cross_for_edge(edge: MilliVec, pivot: MilliVec, ball: MilliVec) -> i32 {
|
||||
.wrapping_sub(edge_from_ball_x.wrapping_mul(edge_from_pivot_y))
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn rounded(value: f64) -> i32 {
|
||||
value.round() as i32
|
||||
}
|
||||
|
||||
fn collision_distance(ball: MilliVec, pivot: MilliVec) -> i32 {
|
||||
rounded(f64::from(ball.x.wrapping_sub(pivot.x)).hypot(f64::from(
|
||||
ball.y.wrapping_sub(pivot.y),
|
||||
)))
|
||||
let dx = Real48::from_i32(ball.x.wrapping_sub(pivot.x)).divide(THOUSAND);
|
||||
let dy = Real48::from_i32(ball.y.wrapping_sub(pivot.y)).divide(THOUSAND);
|
||||
dx.square()
|
||||
.add(dy.square())
|
||||
.sqrt()
|
||||
.multiply(THOUSAND)
|
||||
.round_i32()
|
||||
}
|
||||
|
||||
fn contains(geometry: Geometry, side: FlipperSide, delta: i32, ball: MilliVec) -> Option<i32> {
|
||||
@@ -167,7 +170,11 @@ fn penetration(geometry: Geometry, delta: i32, ball: MilliVec) -> i32 {
|
||||
geometry
|
||||
.pivot
|
||||
.y
|
||||
.wrapping_sub(rounded(f64::from(numerator) / f64::from(edge_dx)))
|
||||
.wrapping_sub(
|
||||
Real48::from_i32(numerator)
|
||||
.divide(Real48::from_i32(edge_dx))
|
||||
.round_i32(),
|
||||
)
|
||||
} else {
|
||||
geometry.positive_edge.y.wrapping_add(43_000)
|
||||
}
|
||||
@@ -185,7 +192,7 @@ pub fn moving_flipper_response(
|
||||
velocity: MilliVec,
|
||||
delta: i32,
|
||||
side: FlipperSide,
|
||||
response_normal: f64,
|
||||
response_normal: Real48,
|
||||
maximum_speed: i32,
|
||||
) -> Option<FlipperResponse> {
|
||||
let geometry = Geometry::for_side(side, delta);
|
||||
@@ -205,7 +212,7 @@ fn response_with_geometry(
|
||||
velocity: MilliVec,
|
||||
delta: i32,
|
||||
side: FlipperSide,
|
||||
response_normal: f64,
|
||||
response_normal: Real48,
|
||||
maximum_speed: i32,
|
||||
geometry: Geometry,
|
||||
) -> Option<FlipperResponse> {
|
||||
@@ -222,20 +229,33 @@ fn response_with_geometry(
|
||||
normal_y = normal_y.wrapping_neg();
|
||||
}
|
||||
|
||||
let tangent_projection = rounded(
|
||||
(f64::from(velocity.x) * f64::from(normal_y)
|
||||
- f64::from(velocity.y) * f64::from(normal_x))
|
||||
/ RESPONSE_RADIUS,
|
||||
);
|
||||
let gain = (f64::from(distance) / RESPONSE_RADIUS).sqrt() * 2.0 + 0.4;
|
||||
let tangent_projection = rounded(f64::from(tangent_projection) * (1.0 + response_normal))
|
||||
.wrapping_add(rounded(f64::from(maximum_speed) * gain));
|
||||
let delta_velocity_x = rounded(
|
||||
-f64::from(tangent_projection) * f64::from(normal_y) / RESPONSE_RADIUS,
|
||||
);
|
||||
let delta_velocity_y = rounded(
|
||||
f64::from(tangent_projection) * f64::from(normal_x) / RESPONSE_RADIUS,
|
||||
);
|
||||
let normal_x = Real48::from_i32(normal_x);
|
||||
let normal_y = Real48::from_i32(normal_y);
|
||||
let response_radius = Real48::from_i32(44_000);
|
||||
let tangent_projection = Real48::from_i32(velocity.x)
|
||||
.multiply(normal_y)
|
||||
.subtract(Real48::from_i32(velocity.y).multiply(normal_x))
|
||||
.divide(response_radius)
|
||||
.round_i32();
|
||||
let gain = Real48::from_i32(distance)
|
||||
.divide(response_radius)
|
||||
.sqrt()
|
||||
.multiply(TWO)
|
||||
.add(TWO_FIFTHS);
|
||||
let tangent_projection = Real48::from_i32(tangent_projection)
|
||||
.multiply(ONE.add(response_normal))
|
||||
.round_i32()
|
||||
.wrapping_add(Real48::from_i32(maximum_speed).multiply(gain).round_i32());
|
||||
let delta_velocity_x = Real48::from_i32(tangent_projection)
|
||||
.multiply(normal_y)
|
||||
.subtract(Real48::ZERO)
|
||||
.divide(response_radius)
|
||||
.round_i32()
|
||||
.wrapping_neg();
|
||||
let delta_velocity_y = Real48::from_i32(tangent_projection)
|
||||
.multiply(normal_x)
|
||||
.divide(response_radius)
|
||||
.round_i32();
|
||||
let velocity = MilliVec {
|
||||
x: velocity.x.wrapping_add(delta_velocity_x),
|
||||
y: velocity.y.wrapping_add(delta_velocity_y),
|
||||
@@ -243,9 +263,9 @@ fn response_with_geometry(
|
||||
if velocity.y == 0 {
|
||||
return None;
|
||||
}
|
||||
let movement_x = rounded(
|
||||
f64::from(penetration.wrapping_mul(velocity.x)) / f64::from(velocity.y),
|
||||
);
|
||||
let movement_x = Real48::from_i32(penetration.wrapping_mul(velocity.x))
|
||||
.divide(Real48::from_i32(velocity.y))
|
||||
.round_i32();
|
||||
Some(FlipperResponse {
|
||||
velocity,
|
||||
movement: MilliVec {
|
||||
@@ -267,7 +287,7 @@ mod tests {
|
||||
MilliVec { x: 1_000, y: 2_000 },
|
||||
-1,
|
||||
FlipperSide::Left,
|
||||
0.5,
|
||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||
MilliVec { x: 1_266, y: 1_511 },
|
||||
MilliVec { x: -6_703, y: -8_000 },
|
||||
),
|
||||
@@ -276,7 +296,7 @@ mod tests {
|
||||
MilliVec { x: -1_000, y: 2_000 },
|
||||
1,
|
||||
FlipperSide::Right,
|
||||
0.75,
|
||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0x40]),
|
||||
MilliVec { x: -737, y: 2_263 },
|
||||
MilliVec { x: -8_549, y: 26_250 },
|
||||
),
|
||||
@@ -285,7 +305,7 @@ mod tests {
|
||||
MilliVec { x: 1_000, y: 2_000 },
|
||||
1,
|
||||
FlipperSide::Left,
|
||||
0.25,
|
||||
Real48::from_bytes([0x7f, 0, 0, 0, 0, 0]),
|
||||
MilliVec { x: 622, y: 2_320 },
|
||||
MilliVec { x: 5_414, y: 20_193 },
|
||||
),
|
||||
@@ -294,7 +314,7 @@ mod tests {
|
||||
MilliVec { x: -1_000, y: 2_000 },
|
||||
-1,
|
||||
FlipperSide::Right,
|
||||
0.0,
|
||||
Real48::ZERO,
|
||||
MilliVec { x: -1_280, y: 1_560 },
|
||||
MilliVec { x: 7_385, y: -9_000 },
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user