#include "../tdkpin_flippers.h" #include "../tdkpin_borland_runtime.h" #include "../tdkpin_collision_records.h" #include "../tdkpin_real48.h" #include #include #include 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; }