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
@@ -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;
}