Files
tdkpin/original/reconstructed/tests/test_flipper_collision.c
T
ddidderr fa3f168467 fix(flippers): match original moving-hit geometry
The clone's moving-flipper gate used the cross-product operands in the
opposite order, mirroring and narrowing the hit wedge. Right-flipper release
also used record 81's first endpoint even though the original reads its second
endpoint. Correct both geometry paths, retain the recovered swept tip bounds,
and add live-binary boundary vectors plus a dense transition regression.

Test Plan:
- `cargo test --workspace --all-targets --all-features` -- passed (134 tests)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` -- passed
- `cargo build --profile production` -- passed
- `LSAN_OPTIONS=detect_leaks=0 ASAN_OPTIONS=detect_leaks=0 bash original/tools/test_reconstructed_c.sh` -- passed
- `python3 original/tools/audit_reconstruction.py --require-complete` -- passed
- `git diff --cached --check` -- passed
2026-08-29 09:15:25 +02:00

299 lines
9.2 KiB
C

#include "../tdkpin_flippers.h"
#include "../tdkpin_borland_runtime.h"
#include "../tdkpin_collision_records.h"
#include "../tdkpin_real48.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static uint8_t g_dgroup[0x5000];
static uint8_t g_objects[0x1000];
static uint8_t g_stack[0x1000];
static Win16FarPtr g_receiver;
static uint16_t g_sounds[4];
static unsigned g_sound_count;
static unsigned g_move_calls;
static int32_t g_move_x;
static int32_t g_move_y;
static unsigned g_load_calls;
static unsigned g_save_calls;
uint8_t g_borland_cpu_level = 2;
_Noreturn void borland_runtime_error(uint16_t code)
{
(void)code;
abort();
}
_Noreturn void win16_integer_divide_fault(void)
{
abort();
}
void tdkpin_play_sound_if_not_tilted(uint16_t sound_number)
{
assert(g_sound_count < sizeof(g_sounds) / sizeof(g_sounds[0]));
g_sounds[g_sound_count++] = sound_number;
}
void tdkpin_move_ball_and_render(
uint16_t caller_bp, int32_t delta_x, int32_t delta_y)
{
assert(caller_bp == 0x0300);
g_move_calls++;
g_move_x = delta_x;
g_move_y = delta_y;
}
void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot)
{
assert(caller_bp == 0x0300);
assert(slot == g_load_calls);
g_load_calls++;
}
void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot)
{
assert(caller_bp == 0x0300);
assert(slot == g_save_calls);
g_save_calls++;
}
static Win16FarPtr record(uint16_t id)
{
return win16_dgroup_pointer((uint16_t)(
0x095b + id * sizeof(TdkpinCollisionRecord)));
}
static void write_i32(uint16_t id, size_t offset, int32_t value)
{
win16_write_u32(record(id), (uint16_t)offset, (uint32_t)value);
}
static void write_real48(
uint16_t id, size_t offset, BorlandReal48 value)
{
Win16FarPtr destination = win16_far_add_offset(
record(id), (uint16_t)offset);
for (uint16_t index = 0; index < sizeof(value.bytes); index++) {
win16_write_u8(
win16_far_add_offset(destination, index), value.bytes[index]);
}
}
static void write_points(
uint16_t id,
int32_t point1_x,
int32_t point1_y,
int32_t point2_x,
int32_t point2_y)
{
write_i32(id, offsetof(TdkpinCollisionRecord, point1_x_milli), point1_x);
write_i32(id, offsetof(TdkpinCollisionRecord, point1_y_milli), point1_y);
write_i32(id, offsetof(TdkpinCollisionRecord, point2_x_milli), point2_x);
write_i32(id, offsetof(TdkpinCollisionRecord, point2_y_milli), point2_y);
}
static void prepare_fixture(void)
{
static const BorlandReal48 one_half =
{{0x80, 0, 0, 0, 0, 0}};
static const BorlandReal48 one_quarter =
{{0x7f, 0, 0, 0, 0, 0}};
static const BorlandReal48 three_quarters =
{{0x80, 0, 0, 0, 0, 0x40}};
static const BorlandReal48 zero =
{{0, 0, 0, 0, 0, 0}};
memset(g_dgroup, 0, sizeof(g_dgroup));
memset(g_objects, 0, sizeof(g_objects));
memset(g_stack, 0, sizeof(g_stack));
win16_reset_segment_bindings();
win16_bind_segment(0x7000, g_objects, sizeof(g_objects), true);
win16_bind_segment(0x7200, g_dgroup, sizeof(g_dgroup), true);
win16_bind_segment(0x7300, g_stack, sizeof(g_stack), true);
win16_set_dgroup_selector(0x7200);
win16_set_stack_state(0x7300, 0);
g_receiver = win16_make_far_pointer(0x7000, 0);
win16_write_stack_u16(0x0306, win16_far_offset(g_receiver));
win16_write_stack_u16(0x0308, win16_far_selector(g_receiver));
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 1);
win16_write_u32(g_receiver, 0x56, 1000);
write_points(65, 103000, 397000, 0, 0);
write_real48(
65,
offsetof(TdkpinCollisionRecord, radius_milli),
borland_i32_to_real48(15000));
write_points(66, 112000, 386000, 141000, 413000);
write_real48(
66, offsetof(TdkpinCollisionRecord, response_normal), one_half);
write_points(68, 130000, 427000, 97000, 411000);
write_real48(
68, offsetof(TdkpinCollisionRecord, response_normal), one_quarter);
write_points(80, 210000, 397000, 0, 0);
write_real48(
80,
offsetof(TdkpinCollisionRecord, radius_milli),
borland_i32_to_real48(15000));
write_points(81, 216000, 411000, 177000, 427000);
write_real48(
81, offsetof(TdkpinCollisionRecord, response_normal), three_quarters);
write_points(83, 172000, 413000, 201000, 386000);
write_real48(
83, offsetof(TdkpinCollisionRecord, response_normal), zero);
g_sound_count = 0;
g_move_calls = 0;
g_move_x = 0;
g_move_y = 0;
g_load_calls = 0;
g_save_calls = 0;
}
static void set_ball(int32_t x, int32_t y, int32_t vx, int32_t vy)
{
win16_write_u32(win16_dgroup_pointer(0x07d3), 0, (uint32_t)x);
win16_write_u32(win16_dgroup_pointer(0x07d7), 0, (uint32_t)y);
win16_write_u32(g_receiver, 0x0baa, (uint32_t)vx);
win16_write_u32(g_receiver, 0x0bae, (uint32_t)vy);
}
static void test_left_negative_contact(void)
{
prepare_fixture();
set_ball(104000, 384000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0baa)) == 1266);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0bae)) == 1511);
assert(g_move_x == -6703 && g_move_y == -8000);
assert(g_sound_count == 1 && g_sounds[0] == 21);
assert(g_move_calls == 1);
}
static void test_right_positive_contact(void)
{
prepare_fixture();
set_ball(209000, 419000, -1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, 1, 2, 0);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0baa)) == -737);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0bae)) == 2263);
assert(g_move_x == -5578 && g_move_y == 17127);
assert(g_sound_count == 1 && g_sounds[0] == 21);
assert(g_move_calls == 1);
}
static void test_live_binary_cross_product_order(void)
{
prepare_fixture();
win16_write_u32(g_receiver, 0x56, 3800);
set_ball(100000, 370000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0baa)) == -189);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0bae)) == 960);
assert(g_move_x == 0 && g_move_y == 0);
assert(g_move_calls == 1);
}
static void test_left_positive_contact(void)
{
prepare_fixture();
set_ball(104000, 421000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, 1, 1, 0);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0baa)) == 622);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0bae)) == 2320);
assert(g_move_x == 5414 && g_move_y == 20193);
assert(g_move_calls == 1);
}
static void test_right_negative_contact(void)
{
prepare_fixture();
set_ball(209000, 385000, -1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 2, 0);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0baa)) == -1280);
assert((int32_t)win16_read_u32(
win16_far_add_offset(g_receiver, 0x0bae)) == 1560);
assert(g_move_x == 7385 && g_move_y == -9000);
assert(g_move_calls == 1);
}
static void test_vertical_edge_fallbacks(void)
{
prepare_fixture();
write_i32(
66,
offsetof(TdkpinCollisionRecord, point2_x_milli),
73000);
set_ball(94000, 371000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
assert(g_move_calls == 1 && g_move_y == -1000);
prepare_fixture();
write_i32(
81,
offsetof(TdkpinCollisionRecord, point2_x_milli),
240000);
write_i32(
81,
offsetof(TdkpinCollisionRecord, point2_y_milli),
411000);
set_ball(197000, 455000, -1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, 1, 2, 0);
assert(g_move_calls == 1 && g_move_y == 1000);
}
static void test_multiball_slot_loop_and_invalid_flipper(void)
{
prepare_fixture();
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 2);
set_ball(300000, 300000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
assert(g_load_calls == 2 && g_save_calls == 2);
assert(g_move_calls == 0);
prepare_fixture();
win16_write_u8(win16_far_add_offset(g_receiver, 0x61), 0);
tdkpin_move_flipper_collision_geometry(0x0300, 7, 3, 0);
assert(g_sound_count == 1 && g_sounds[0] == 21);
assert(g_move_calls == 0 && g_load_calls == 0 && g_save_calls == 0);
}
static void test_gates(void)
{
prepare_fixture();
set_ball(300000, 300000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 0);
assert(g_move_calls == 0);
prepare_fixture();
set_ball(104000, 384000, 1000, 2000);
tdkpin_move_flipper_collision_geometry(0x0300, -1, 1, 1);
assert(g_move_calls == 0);
}
int main(void)
{
test_left_negative_contact();
test_right_positive_contact();
test_live_binary_cross_product_order();
test_left_positive_contact();
test_right_negative_contact();
test_vertical_edge_fallbacks();
test_multiball_slot_loop_and_invalid_flipper();
test_gates();
return 0;
}