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
+38 -14
View File
@@ -191,7 +191,7 @@ static int32_t normal_velocity_checked(
velocity_x, velocity_y, normal_x, normal_y, normal_length);
}
static int32_t tangent_velocity_unchecked(
static int32_t tangent_direction_unchecked(
int32_t velocity_x,
int32_t velocity_y,
BorlandReal48 normal_x,
@@ -207,6 +207,22 @@ static int32_t tangent_velocity_unchecked(
borland_i32_to_real48(normal_length))));
}
static int32_t spin_velocity_unchecked(
int32_t velocity_x,
int32_t velocity_y,
BorlandReal48 normal_x,
BorlandReal48 normal_y,
int32_t normal_length)
{
return borland_real48_round_to_i32(borland_real48_divide(
borland_real48_add(
borland_real48_multiply(
borland_i32_to_real48(velocity_x), normal_x),
borland_real48_multiply(
borland_i32_to_real48(velocity_y), normal_y)),
borland_i32_to_real48(normal_length)));
}
static int32_t speed_milli(int32_t velocity_x, int32_t velocity_y)
{
return distance_milli(velocity_x, velocity_y);
@@ -927,7 +943,16 @@ static void apply_collision_response(
tdkpin_add_score_from_caller_frame(caller_bp, candidate->score);
}
int32_t length = candidate_normal_length(candidate);
int32_t projection = tangent_velocity_unchecked(
/* Raw 1000:ef19..f16a keeps the contact-direction and spin projections
* separate. The swapped-component value selects the sign of the current
* tangential response; the standard dot product updates retained spin. */
int32_t tangent_direction = tangent_direction_unchecked(
motion->velocity_x,
motion->velocity_y,
candidate->normal_x,
candidate->normal_y,
length);
int32_t spin_projection = spin_velocity_unchecked(
motion->velocity_x,
motion->velocity_y,
candidate->normal_x,
@@ -936,14 +961,9 @@ static void apply_collision_response(
BorlandReal48 spin_delta = borland_real48_multiply(
borland_real48_multiply(
borland_i32_to_real48(projection), g_real48_point_zero_two),
borland_i32_to_real48(spin_projection),
g_real48_point_zero_two),
candidate->response_tangent);
spin_delta = borland_real48_multiply(
borland_i32_to_real48(absolute_wrap(candidate->normal_velocity)),
spin_delta);
if (projection < 0) {
spin_delta = negate_real48(spin_delta);
}
BorlandReal48 discarded = borland_real48_divide(
borland_real48_sqrt(borland_real48_multiply(
absolute_real48(spin_delta), g_real48_twenty)),
@@ -951,17 +971,21 @@ static void apply_collision_response(
(void)discarded;
discarded = (BorlandReal48){{0,0,0,0,0,0}};
projection = borland_real48_round_to_i32(borland_real48_add(
BorlandReal48 tangent_response = borland_real48_multiply(
borland_i32_to_real48(absolute_wrap(candidate->normal_velocity)),
candidate->response_tangent);
if (tangent_direction < 0) {
tangent_response = negate_real48(tangent_response);
}
int32_t projection = borland_real48_round_to_i32(borland_real48_add(
borland_real48_subtract(
borland_real48_multiply(motion->spin, g_real48_point_four),
borland_real48_divide(
borland_real48_multiply(
borland_i32_to_real48(absolute_wrap(projection)),
borland_i32_to_real48(absolute_wrap(tangent_direction)),
discarded),
borland_i32_to_real48(motion->speed))),
borland_real48_multiply(
borland_i32_to_real48(projection),
candidate->response_tangent)));
tangent_response));
motion->spin = borland_real48_add(
borland_real48_multiply(motion->spin, g_real48_point_six),
spin_delta);