fix(physics): restore relative slingshot response

A full comparison against the 175-record ledger found stale guessed coordinates
in both relative slingshot chains. Circles 56/58/71/73 and lines 57/59/72/74
were displaced by up to 70 pixels. The corrected record-57 geometry then
exposed a second issue: readable C and Rust had collapsed two independent
binary response projections into one.

Transcribe the accumulated initializer coordinates exactly. Use the
swapped-component projection only to sign the current tangential response
`abs(normal_velocity) * response_tangent`, and use the standard dot product for
`spin_delta * 0.02 * response_tangent`. Correct the readable C evidence and
Rust together, extend live tracing with the six-byte spin field, and seal the
stopped-Wine transition from `(75530,354765)/(3000,15)` to
`(77369,356597)/(1839,1832)` with Real48 spin `-5.28` in both harnesses.

Test Plan:
- stopped Wine 11.15 record-57 injection/trace -- matched position, velocity, and spin
- `cargo test --workspace --all-targets --all-features` -- 123 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed with zero incomplete or unclassified units
- `python3 -m py_compile original/tools/trace_original_state.py original/tools/inject_original_state.py` -- passed
- `rumdl check --flavor commonmark RECONSTRUCTION.md CHANGELOG.md` -- passed
- `git diff --cached --check` -- passed
This commit is contained in:
2026-08-23 20:28:29 +02:00
parent 9b56247c32
commit f79652bc9f
11 changed files with 222 additions and 49 deletions
+23 -8
View File
@@ -241,7 +241,7 @@ 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 {
fn tangent_direction(velocity: MilliVec, normal_x: Real48, normal_y: Real48, length: i32) -> i32 {
if length <= 0 {
return 0;
}
@@ -255,6 +255,17 @@ fn tangent_velocity(velocity: MilliVec, normal_x: Real48, normal_y: Real48, leng
.round_i32()
}
fn spin_velocity(velocity: MilliVec, normal_x: Real48, normal_y: Real48, length: i32) -> i32 {
if length <= 0 {
return 0;
}
Real48::from_i32(velocity.x)
.multiply(normal_x)
.add(Real48::from_i32(velocity.y).multiply(normal_y))
.divide(Real48::from_i32(length))
.round_i32()
}
fn cross_at_endpoint(
point: MilliVec,
current: MilliVec,
@@ -284,17 +295,21 @@ fn apply_response(
x: normal_x.round_i32(),
y: normal_y.round_i32(),
});
let initial_projection = tangent_velocity(velocity, normal_x, normal_y, length);
let mut spin_delta = Real48::from_i32(initial_projection)
// Raw 1000:ef19..f16a uses the swapped-component value only as the
// response direction; the standard dot product independently updates spin.
let tangent_direction = tangent_direction(velocity, normal_x, normal_y, length);
let spin_projection = spin_velocity(velocity, normal_x, normal_y, length);
let spin_delta = Real48::from_i32(spin_projection)
.multiply(Real48::from_bytes([0x7b, 0x71, 0x3d, 0x0a, 0xd7, 0x23]))
.multiply(coefficients.tangent)
.multiply(Real48::from_i32(collision_velocity.wrapping_abs()));
if initial_projection < 0 {
spin_delta = spin_delta.negate();
.multiply(coefficients.tangent);
let mut tangent_response = Real48::from_i32(collision_velocity.wrapping_abs())
.multiply(coefficients.tangent);
if tangent_direction < 0 {
tangent_response = tangent_response.negate();
}
let projection = spin
.multiply(Real48::from_bytes([0x7f, 0xcd, 0xcc, 0xcc, 0xcc, 0x4c]))
.add(Real48::from_i32(initial_projection).multiply(coefficients.tangent))
.add(tangent_response)
.round_i32();
let spin = spin
.multiply(Real48::from_bytes([0x80, 0x9a, 0x99, 0x99, 0x99, 0x19]))