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:
@@ -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);
|
||||
|
||||
@@ -473,6 +473,51 @@ static void test_out_of_bounds_and_zero_substeps_multiball(void)
|
||||
assert(g_render_calls == 2 && g_update_calls == 0);
|
||||
}
|
||||
|
||||
static void test_live_relative_record_57_transition(void)
|
||||
{
|
||||
prepare_fixture();
|
||||
set_ball(72530, 354750, 3000, 0);
|
||||
win16_write_u32(g_window, 0x5a, 15);
|
||||
win16_write_u32(g_window, 0x56, 3800);
|
||||
TdkpinCollisionRecord segment = base_record(2);
|
||||
segment.bounds_min_x_milli = 45000;
|
||||
segment.bounds_min_y_milli = 335000;
|
||||
segment.bounds_max_x_milli = 102000;
|
||||
segment.bounds_max_y_milli = 371000;
|
||||
segment.point1_x_milli = 97000;
|
||||
segment.point1_y_milli = 366000;
|
||||
segment.point2_x_milli = 50000;
|
||||
segment.point2_y_milli = 340000;
|
||||
segment.response_normal =
|
||||
(BorlandReal48){{0x80,0x00,0x00,0x00,0x00,0x00}};
|
||||
segment.response_tangent =
|
||||
(BorlandReal48){{0x7d,0xcd,0xcc,0xcc,0xcc,0x4c}};
|
||||
segment.layer_mask = 2;
|
||||
write_record(57, &segment);
|
||||
|
||||
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_dgroup_pointer(0x07d3)) == 75530);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_dgroup_pointer(0x07d7)) == 354765);
|
||||
tdkpin_simulate_ball_and_dispatch_collision(0x0300);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_dgroup_pointer(0x07d3)) == 77369);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_dgroup_pointer(0x07d7)) == 356597);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_window, 0x0baa)) == 1839);
|
||||
assert((int32_t)win16_read_u32(
|
||||
win16_far_add_offset(g_window, 0x0bae)) == 1832);
|
||||
static const uint8_t expected_spin[6] = {
|
||||
0x83, 0x5d, 0x8f, 0xc2, 0xf5, 0xa8};
|
||||
for (uint16_t index = 0; index < 6; index++) {
|
||||
assert(win16_read_u8(win16_far_add_offset(
|
||||
g_window, (uint16_t)(0x0bce + index))) ==
|
||||
expected_spin[index]);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_free_integration_and_speed_clamp();
|
||||
@@ -480,5 +525,6 @@ int main(void)
|
||||
test_capture_and_trigger_records();
|
||||
test_capture_boundary_and_claw();
|
||||
test_out_of_bounds_and_zero_substeps_multiball();
|
||||
test_live_relative_record_57_transition();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user