fix(physics): restore swapped-component tangent driver

Use the binary tangent velocity -(vx*normal_y + vy*normal_x)/length, then combine it with retained spin and the Real48 response coefficient. This single formula matches live outer/inner shooter walls, object 155, bumper 51, and the horizontal special-drain response; update C evidence and keep autoplay coverage on a target-reaching seed.

Test Plan:
- bash original/tools/test_reconstructed_c.sh
- python3 original/tools/audit_reconstruction.py --require-complete
- cargo test --all-targets
- cargo clippy --all-targets --all-features -- -D warnings
- original Wine traces: /tmp/original-outer-wall.csv /tmp/original-inner-wall.csv /tmp/original-circle155-after.csv /tmp/original-special-respawn.csv
- git diff --check
This commit is contained in:
2026-08-23 19:48:30 +02:00
parent 3dce159965
commit 6f8c55657b
6 changed files with 47 additions and 15 deletions
+2 -2
View File
@@ -10,8 +10,8 @@ and this project adheres to
### Fixed
- Correct the common Real48 tangent projection to
`spin*0.4 + normal_velocity*response_tangent`; live one-substep probes now
- Correct the common Real48 tangent projection to the binary's
`spin*0.4 - ((vx*ny + vy*nx)/length)*response_tangent`; live probes now
match horizontal drain `(-302,-1809)`, ordinary circle `(94,564)`, and
bumper `(197,3096)` transitions instead of rounding a bare coefficient away.
- Gate dynamic record 174/175 impulse transfer on the other slot's capture age:
+21 -7
View File
@@ -241,6 +241,20 @@ fn normal_velocity(velocity: MilliVec, normal_x: Real48, normal_y: Real48, lengt
.round_i32()
}
fn tangent_velocity(velocity: MilliVec, normal_x: Real48, normal_y: Real48, length: i32) -> i32 {
if length <= 0 {
return 0;
}
Real48::ZERO
.subtract(
Real48::from_i32(velocity.x)
.multiply(normal_y)
.add(Real48::from_i32(velocity.y).multiply(normal_x)),
)
.divide(Real48::from_i32(length))
.round_i32()
}
fn cross_at_endpoint(
point: MilliVec,
current: MilliVec,
@@ -270,7 +284,7 @@ fn apply_response(
x: normal_x.round_i32(),
y: normal_y.round_i32(),
});
let initial_projection = normal_velocity(velocity, normal_x, normal_y, length);
let initial_projection = tangent_velocity(velocity, normal_x, normal_y, length);
let mut spin_delta = Real48::from_i32(initial_projection)
.multiply(Real48::from_bytes([0x7b, 0x71, 0x3d, 0x0a, 0xd7, 0x23]))
.multiply(coefficients.tangent)
@@ -280,7 +294,7 @@ fn apply_response(
}
let projection = spin
.multiply(Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]))
.add(Real48::from_i32(collision_velocity).multiply(coefficients.tangent))
.add(Real48::from_i32(initial_projection).multiply(coefficients.tangent))
.round_i32();
let spin = spin
.multiply(Real48::from_bytes([0x80, 0x9a, 0x99, 0x99, 0x99, 0x19]))
@@ -677,12 +691,12 @@ mod tests {
0.6,
0.1,
));
assert_eq!(velocity, MilliVec { x: -1_800, y: 345 });
assert_eq!(velocity, MilliVec { x: -1_800, y: -255 });
assert_eq!(
old.add(velocity),
MilliVec {
x: 324_200,
y: 200_390
y: 199_790
}
);
}
@@ -705,7 +719,7 @@ mod tests {
let response = candidate.resolve(MilliVec { x: 3_000, y: 1_000 }, Real48::ZERO);
assert_eq!(response.velocity, MilliVec { x: -1_800, y: 1_300 });
assert_eq!(response.velocity, MilliVec { x: -1_800, y: 700 });
assert_eq!(response.surface_distance, 2_000);
}
@@ -779,12 +793,12 @@ mod tests {
0.6,
0.1,
));
assert_eq!(velocity, MilliVec { x: 1_080, y: -390 });
assert_eq!(velocity, MilliVec { x: 1_080, y: -30 });
assert_eq!(
old.add(velocity),
MilliVec {
x: 321_680,
y: 198_935
y: 199_295
}
);
}
+1 -1
View File
@@ -433,7 +433,7 @@ mod tests {
#[test]
fn autoplay_stays_finite_for_two_minutes_of_exact_physics() {
let mut simulation = Simulation::new(Scenario::Autoplay, 7);
let mut simulation = Simulation::new(Scenario::Autoplay, 1);
simulation.advance_to(u64::from(SIMULATION_HZ) * 120);
let event_count = |name: &str| {
simulation