fix(flippers): match original moving-hit geometry

The clone's moving-flipper gate used the cross-product operands in the
opposite order, mirroring and narrowing the hit wedge. Right-flipper release
also used record 81's first endpoint even though the original reads its second
endpoint. Correct both geometry paths, retain the recovered swept tip bounds,
and add live-binary boundary vectors plus a dense transition regression.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- passed (134 tests)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- `LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-29 09:15:25 +02:00
parent faf66f1ac0
commit fa3f168467
5 changed files with 517 additions and 55 deletions
+46 -3
View File
@@ -168,9 +168,19 @@ impl MilliVec {
if speed <= maximum {
return;
}
let scale = Real48::from_i32(maximum).divide(Real48::from_i32(speed));
self.x = Real48::from_i32(self.x).multiply(scale).round_i32();
self.y = Real48::from_i32(self.y).multiply(scale).round_i32();
// The original computes the excess fraction and subtracts the
// rounded component from each axis. Scaling directly by
// `maximum / speed` is mathematically equivalent, but can differ by
// one millipixel because each Real48 operation is rounded
// independently.
let excess = Real48::from_i32(speed.wrapping_sub(maximum))
.divide(Real48::from_i32(speed));
self.x = self
.x
.wrapping_sub(Real48::from_i32(self.x).multiply(excess).round_i32());
self.y = self
.y
.wrapping_sub(Real48::from_i32(self.y).multiply(excess).round_i32());
}
}
@@ -676,6 +686,39 @@ mod tests {
}
}
#[test]
fn speed_clamp_matches_reconstructed_formula() {
let maximum = 3_800;
let mut mismatches = Vec::new();
for x in (-10_000..=10_000).step_by(37) {
for y in (-10_000..=10_000).step_by(41) {
let input = MilliVec { x, y };
let speed = milli_distance(input);
if speed <= maximum {
continue;
}
let excess = Real48::from_i32(speed - maximum)
.divide(Real48::from_i32(speed));
let expected = MilliVec {
x: x.wrapping_sub(Real48::from_i32(x).multiply(excess).round_i32()),
y: y.wrapping_sub(Real48::from_i32(y).multiply(excess).round_i32()),
};
let mut actual = input;
actual.clamp_speed(maximum);
if actual != expected {
mismatches.push((input, speed, actual, expected));
if mismatches.len() == 5 {
break;
}
}
}
if mismatches.len() == 5 {
break;
}
}
assert!(mismatches.is_empty(), "speed-clamp mismatches: {mismatches:?}");
}
#[test]
fn outer_shooter_wall_matches_the_live_tangent_response() {
let old = MilliVec {