fix(physics): replace fitted flipper transfer
Remove the live-fitted upward and downward flipper polynomials. Port the reconstructed 1000:7ed9 path with delta-specific pivots and record edges, integer cross-product and radius gates, penetration, response-record gain, wrapping velocity updates, and the final position delta. Use resting records on press and the already-raised records on release, correct the original delta direction, and apply each edge to both live ball slots. Keep render-facing Vec2 projections, but adjust their float representation by ULPs so every gameplay millipixel round-trips exactly instead of losing reconstructed integer results. Test Plan: - `bash original/tools/test_reconstructed_c.sh` -- passed during raised-record reference capture - `cargo test --all-targets` -- passed, 62 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:
@@ -35,19 +35,18 @@ impl MilliVec {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_millipixels(value: Vec2) -> Self {
|
||||
Self {
|
||||
x: value.x.round() as i32,
|
||||
y: value.y.round() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_position(self) -> Vec2 {
|
||||
vec2(self.x as f32 / 1_000.0, self.y as f32 / 1_000.0)
|
||||
vec2(
|
||||
scaled_f32_with_exact_roundtrip(self.x, 1_000.0),
|
||||
scaled_f32_with_exact_roundtrip(self.y, 1_000.0),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_velocity_per_second(self) -> Vec2 {
|
||||
vec2(self.x as f32 / 10.0, self.y as f32 / 10.0)
|
||||
vec2(
|
||||
scaled_f32_with_exact_roundtrip(self.x, 10.0),
|
||||
scaled_f32_with_exact_roundtrip(self.y, 10.0),
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn add(self, other: Self) -> Self {
|
||||
@@ -68,6 +67,23 @@ impl MilliVec {
|
||||
}
|
||||
}
|
||||
|
||||
fn scaled_f32_with_exact_roundtrip(value: i32, scale: f32) -> f32 {
|
||||
let mut projected = value as f32 / scale;
|
||||
for _ in 0..4 {
|
||||
let recovered = (projected * scale).round() as i32;
|
||||
if recovered == value {
|
||||
return projected;
|
||||
}
|
||||
projected = if recovered < value {
|
||||
projected.next_up()
|
||||
} else {
|
||||
projected.next_down()
|
||||
};
|
||||
}
|
||||
debug_assert_eq!((projected * scale).round() as i32, value);
|
||||
projected
|
||||
}
|
||||
|
||||
const fn cross(left: MilliVec, right: MilliVec) -> i64 {
|
||||
left.x as i64 * right.y as i64 - left.y as i64 * right.x as i64
|
||||
}
|
||||
@@ -308,6 +324,14 @@ pub fn path_intersects_circle(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn float_views_roundtrip_every_gameplay_velocity_millipixel() {
|
||||
for value in -10_000..=10_000 {
|
||||
let milli = MilliVec { x: value, y: -value };
|
||||
assert_eq!(MilliVec::from_velocity_per_second(milli.to_velocity_per_second()), milli);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outer_shooter_wall_matches_the_live_original_probe() {
|
||||
let old = MilliVec {
|
||||
|
||||
Reference in New Issue
Block a user