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:
2026-08-23 17:11:48 +02:00
parent c1d8e5755f
commit d72db79af6
7 changed files with 392 additions and 92 deletions
+33 -9
View File
@@ -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 {