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:
@@ -54,9 +54,11 @@ impl Geometry {
|
||||
x: 152_000,
|
||||
y: 413_000,
|
||||
},
|
||||
// The binary reads record 81 point 1 but uses point 2 for this
|
||||
// edge. On release that point is already in its raised state.
|
||||
positive_edge: MilliVec {
|
||||
x: if delta == 1 { 177_000 } else { 196_000 },
|
||||
y: if delta == 1 { 405_000 } else { 411_000 },
|
||||
x: if delta == 1 { 151_000 } else { 157_000 },
|
||||
y: if delta == 1 { 378_000 } else { 427_000 },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -71,8 +73,8 @@ impl Geometry {
|
||||
y: 427_000,
|
||||
},
|
||||
FlipperSide::Right => MilliVec {
|
||||
x: 196_000,
|
||||
y: 411_000,
|
||||
x: 157_000,
|
||||
y: 427_000,
|
||||
},
|
||||
};
|
||||
geometry
|
||||
@@ -84,9 +86,11 @@ fn cross_for_edge(edge: MilliVec, pivot: MilliVec, ball: MilliVec) -> i32 {
|
||||
let edge_from_pivot_y = edge.y.wrapping_sub(pivot.y) / 1_000;
|
||||
let edge_from_ball_y = edge.y.wrapping_sub(ball.y) / 1_000;
|
||||
let edge_from_pivot_x = edge.x.wrapping_sub(pivot.x) / 1_000;
|
||||
edge_from_ball_y
|
||||
.wrapping_mul(edge_from_pivot_x)
|
||||
.wrapping_sub(edge_from_ball_x.wrapping_mul(edge_from_pivot_y))
|
||||
// 1000:8208 computes C*D first, then computes A*B and subtracts C*D.
|
||||
// Reversing these operands mirrors the complete moving-hit wedge.
|
||||
edge_from_ball_x
|
||||
.wrapping_mul(edge_from_pivot_y)
|
||||
.wrapping_sub(edge_from_ball_y.wrapping_mul(edge_from_pivot_x))
|
||||
}
|
||||
|
||||
fn collision_distance(ball: MilliVec, pivot: MilliVec) -> i32 {
|
||||
@@ -279,6 +283,61 @@ fn response_with_geometry(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn hash_u32(mut hash: u64, value: u32) -> u64 {
|
||||
for byte in value.to_le_bytes() {
|
||||
hash ^= u64::from(byte);
|
||||
hash = hash.wrapping_mul(1_099_511_628_211);
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
const fn i32_bits(value: i32) -> u32 {
|
||||
u32::from_ne_bytes(value.to_ne_bytes())
|
||||
}
|
||||
|
||||
fn dense_transition_digest(side: FlipperSide, delta: i32) -> (u32, u64) {
|
||||
let velocities = [
|
||||
MilliVec { x: 0, y: 2_000 },
|
||||
MilliVec { x: 1_000, y: 2_000 },
|
||||
MilliVec { x: -1_000, y: 2_000 },
|
||||
MilliVec { x: 2_700, y: -2_700 },
|
||||
];
|
||||
let mut hash = 14_695_981_039_346_656_037_u64;
|
||||
let mut hits = 0_u32;
|
||||
for x in (40_000..=280_000).step_by(1_000) {
|
||||
for y in (320_000..=470_000).step_by(1_000) {
|
||||
for (velocity_index, input_velocity) in velocities.into_iter().enumerate() {
|
||||
let response = moving_flipper_response(
|
||||
MilliVec { x, y },
|
||||
input_velocity,
|
||||
delta,
|
||||
side,
|
||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||
3_800,
|
||||
);
|
||||
let (hit, velocity, movement) = response.map_or(
|
||||
(0_u32, input_velocity, MilliVec::default()),
|
||||
|response| (1, response.velocity, response.movement),
|
||||
);
|
||||
hits += hit;
|
||||
for value in [
|
||||
i32_bits(x),
|
||||
i32_bits(y),
|
||||
u32::try_from(velocity_index).expect("four velocities fit u32"),
|
||||
hit,
|
||||
i32_bits(velocity.x),
|
||||
i32_bits(velocity.y),
|
||||
i32_bits(movement.x),
|
||||
i32_bits(movement.y),
|
||||
] {
|
||||
hash = hash_u32(hash, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(hits, hash)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_direction_vectors_match_the_reconstructed_c_harness() {
|
||||
let cases = [
|
||||
@@ -298,7 +357,7 @@ mod tests {
|
||||
FlipperSide::Right,
|
||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0x40]),
|
||||
MilliVec { x: -737, y: 2_263 },
|
||||
MilliVec { x: -8_549, y: 26_250 },
|
||||
MilliVec { x: -5_578, y: 17_127 },
|
||||
),
|
||||
(
|
||||
MilliVec { x: 104_000, y: 421_000 },
|
||||
@@ -334,4 +393,54 @@ mod tests {
|
||||
assert_eq!(result.movement, expected_movement);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boundary_vectors_match_the_live_original_binary() {
|
||||
for (ball, velocity, delta, side, expected_velocity, expected_movement) in [
|
||||
(
|
||||
MilliVec { x: 100_000, y: 370_000 },
|
||||
MilliVec { x: 1_000, y: 2_000 },
|
||||
-1,
|
||||
FlipperSide::Left,
|
||||
MilliVec { x: -189, y: 960 },
|
||||
MilliVec::default(),
|
||||
),
|
||||
(
|
||||
MilliVec { x: 209_000, y: 419_000 },
|
||||
MilliVec { x: 1_000, y: 2_000 },
|
||||
1,
|
||||
FlipperSide::Right,
|
||||
MilliVec { x: 2_133, y: 3_133 },
|
||||
MilliVec { x: 5_743, y: 8_435 },
|
||||
),
|
||||
] {
|
||||
let response = moving_flipper_response(
|
||||
ball,
|
||||
velocity,
|
||||
delta,
|
||||
side,
|
||||
Real48::from_bytes([0x80, 0, 0, 0, 0, 0]),
|
||||
3_800,
|
||||
)
|
||||
.expect("the live original hits this flipper boundary");
|
||||
assert_eq!(response.velocity, expected_velocity);
|
||||
assert_eq!(response.movement, expected_movement);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dense_transition_matrix_matches_the_reconstructed_c_oracle() {
|
||||
for (side, delta, expected_hits, expected_hash) in [
|
||||
(FlipperSide::Left, -1, 9_704, 0x280f_c478_572e_ccc5),
|
||||
(FlipperSide::Left, 1, 10_256, 0x65f5_a7da_3013_c0f5),
|
||||
(FlipperSide::Right, -1, 9_704, 0x875b_7dd6_1574_7fac),
|
||||
(FlipperSide::Right, 1, 9_512, 0x4713_d619_16d8_0fe1),
|
||||
] {
|
||||
assert_eq!(
|
||||
dense_transition_digest(side, delta),
|
||||
(expected_hits, expected_hash),
|
||||
"moving-flipper matrix mismatch for {side:?} delta {delta}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user