#include "../tdkpin_physics.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_window; static unsigned g_finish_calls; static unsigned g_render_calls; static unsigned g_update_calls; static int32_t g_last_update; static unsigned g_restore_b_calls; static uint16_t g_last_restore_b; static unsigned g_event_calls; static uint16_t g_last_event_id; static uint16_t g_last_event_flags; static unsigned g_sound_calls; static uint16_t g_last_sound; static int32_t g_score_total; 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_finish_ball_or_advance_player(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_finish_calls++; } void tdkpin_render_ball_swept_region(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_render_calls++; } void tdkpin_update_dynamic_ball_record( uint16_t caller_bp, int32_t displacement) { assert(caller_bp == 0x0300); g_update_calls++; g_last_update = displacement; } void tdkpin_restore_record_b(uint16_t caller_bp, uint16_t id) { assert(caller_bp == 0x0300); g_restore_b_calls++; g_last_restore_b = id; } void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id) { (void)caller_bp; (void)id; } void tdkpin_handle_collision_events( uint16_t caller_bp, uint16_t event_flags, uint16_t record_id) { assert(caller_bp == 0x0300); g_event_calls++; g_last_event_flags = event_flags; g_last_event_id = record_id; } void tdkpin_play_sound_if_not_tilted(uint16_t sound_number) { g_sound_calls++; g_last_sound = sound_number; } void tdkpin_add_score_from_caller_frame(uint16_t caller_bp, int32_t delta) { assert(caller_bp == 0x0300); g_score_total += delta; } void tdkpin_draw_score_from_caller_frame( uint16_t caller_bp, uint16_t value_low, uint16_t value_high) { (void)caller_bp; (void)value_low; (void)value_high; } void tdkpin_refresh_player_progress_markers(uint16_t caller_bp) { (void)caller_bp; } void tdkpin_apply_player_sprite_effect( uint16_t caller_bp, int16_t argument) { (void)caller_bp; (void)argument; } void tdkpin_composite_player_sprite(uint16_t sprite_index, HDC16 reference_dc) { (void)sprite_index; (void)reference_dc; } void tdkpin_present_background_region( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { (void)height; (void)width; (void)y; (void)x; (void)dc; } HDC16 GetDC16(HWND16 window) { (void)window; return 0x3333; } INT16 ReleaseDC16(HWND16 window, HDC16 dc) { (void)window; (void)dc; return 1; } void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot) { (void)caller_bp; (void)slot; g_load_calls++; } void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot) { (void)caller_bp; (void)slot; g_save_calls++; } uint16_t borland_random_below(uint16_t upper_bound) { assert(upper_bound == 4); return 0; } BorlandReal48 borland_random_real48_registers(void) { return (BorlandReal48){{0x80,0,0,0,0,0}}; } static bool in_bounds( int32_t x, int32_t y, int32_t maximum_y, int32_t maximum_x, int32_t minimum_y, int32_t minimum_x) { return x >= minimum_x && x <= maximum_x && y >= minimum_y && y <= maximum_y; } uint16_t tdkpin_predicted_ball_in_bounds( uint16_t ignored_static_link, int32_t maximum_y, int32_t maximum_x, int32_t minimum_y, int32_t minimum_x) { (void)ignored_static_link; return in_bounds( (int32_t)win16_read_u32(win16_dgroup_pointer(0x07db)), (int32_t)win16_read_u32(win16_dgroup_pointer(0x07df)), maximum_y, maximum_x, minimum_y, minimum_x); } uint16_t tdkpin_receiver_point_in_bounds( uint16_t caller_bp, int32_t maximum_y, int32_t maximum_x, int32_t minimum_y, int32_t minimum_x) { assert(caller_bp == 0x0300); return in_bounds( (int32_t)win16_read_u32(win16_far_add_offset(g_window, 0x0bba)), (int32_t)win16_read_u32(win16_far_add_offset(g_window, 0x0bbe)), maximum_y, maximum_x, minimum_y, minimum_x); } static Win16FarPtr record_pointer(uint16_t id) { return win16_dgroup_pointer((uint16_t)( 0x095b + id * sizeof(TdkpinCollisionRecord))); } static void write_record( uint16_t id, const TdkpinCollisionRecord *record) { const uint8_t *bytes = (const uint8_t *)record; for (uint16_t index = 0; index < sizeof(*record); index++) { win16_write_u8( win16_far_add_offset(record_pointer(id), index), bytes[index]); } } static TdkpinCollisionRecord base_record(uint8_t type) { TdkpinCollisionRecord record = {0}; record.type = type; record.subtype = 1; record.active = 1; record.bounds_min_x_milli = 0; record.bounds_min_y_milli = 0; record.bounds_max_x_milli = 200000; record.bounds_max_y_milli = 200000; record.layer_mask = 1; return record; } static void prepare_fixture(void) { 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_window = win16_make_far_pointer(0x7000, 0); win16_write_stack_u16(0x0306, win16_far_offset(g_window)); win16_write_stack_u16(0x0308, win16_far_selector(g_window)); win16_write_u16(g_window, 4, 0x2222); win16_write_u8(win16_far_add_offset(g_window, 0x61), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1); win16_write_u16(win16_dgroup_pointer(0x0871), 0, 1); win16_write_u32(g_window, 0x56, 10000); g_finish_calls = g_render_calls = g_update_calls = 0; g_restore_b_calls = g_event_calls = g_sound_calls = 0; g_score_total = 0; g_load_calls = g_save_calls = 0; } static void set_ball( int32_t x, int32_t y, int32_t velocity_x, int32_t velocity_y) { 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_window, 0x0baa, (uint32_t)velocity_x); win16_write_u32(g_window, 0x0bae, (uint32_t)velocity_y); } static void test_free_integration_and_speed_clamp(void) { prepare_fixture(); set_ball(100000, 100000, 1000, 2000); win16_write_u32(g_window, 0x5a, 100); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 101000); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 102100); assert(g_update_calls == 1 && g_last_update == 1); assert(g_render_calls == 1); assert(win16_read_u32(win16_far_add_offset(g_window, 0x4d)) == 1); prepare_fixture(); set_ball(100000, 100000, 3000, 4000); win16_write_u32(g_window, 0x56, 1000); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) == 600); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == 800); } static void test_circle_and_segment_collisions(void) { prepare_fixture(); set_ball(100000, 100000, 1000, 0); TdkpinCollisionRecord circle = base_record(1); circle.point1_x_milli = 103000; circle.point1_y_milli = 100000; circle.radius_milli = borland_i32_to_real48(4000); write_record(1, &circle); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) == 0); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 100000); prepare_fixture(); set_ball(100000, 100000, 0, 1000); TdkpinCollisionRecord segment = base_record(2); segment.point1_x_milli = 50000; segment.point1_y_milli = 101000; segment.point2_x_milli = 150000; segment.point2_y_milli = 101000; write_record(1, &segment); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == 0); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 100000); prepare_fixture(); set_ball(100000, 100000, 1000, 0); circle = base_record(1); circle.point1_x_milli = 103000; circle.point1_y_milli = 100000; circle.radius_milli = borland_i32_to_real48(4000); circle.response_normal = borland_i32_to_real48(1); circle.flags = 0x0020; circle.score_low = 100; write_record(1, &circle); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) == -1000); assert(g_event_calls == 1 && g_last_event_flags == 0x0020); assert(g_score_total == 100); } static void test_capture_and_trigger_records(void) { prepare_fixture(); set_ball(100000, 100000, 0, 0); TdkpinCollisionRecord capture = base_record(3); capture.point1_x_milli = 100000; capture.point1_y_milli = 100000; capture.radius_milli = borland_i32_to_real48(20000); write_record(50, &capture); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert(win16_read_u16(win16_far_add_offset(record_pointer(50), 0x43)) == 1); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0bca)) == 5); assert(g_sound_calls == 1 && g_last_sound == 15); prepare_fixture(); set_ball(100000, 100000, 1000, 0); TdkpinCollisionRecord trigger = base_record(4); trigger.point1_x_milli = 0; trigger.point1_y_milli = 0; trigger.point2_x_milli = 200000; trigger.point2_y_milli = 200000; write_record(6, &trigger); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) == 900); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == -8500); assert(g_restore_b_calls == 1 && g_last_restore_b == 6); prepare_fixture(); set_ball(100000, 100000, 1000, 0); win16_write_u8(win16_dgroup_pointer(0x07d1), 1); win16_write_u8(win16_dgroup_pointer(0x081b), 2); trigger = base_record(4); trigger.point1_x_milli = 101000; trigger.point1_y_milli = 100000; trigger.radius_milli = borland_i32_to_real48(5000); trigger.flags = 0x0040; trigger.score_low = 100; write_record(10, &trigger); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert((win16_read_u16( win16_far_add_offset(record_pointer(10), 0x41)) & 0x4000) != 0); assert(g_sound_calls == 1 && g_last_sound == 4); assert(g_event_calls == 1 && g_last_event_id == 10 && g_last_event_flags == 0x4040); assert(g_score_total == 200); } static void test_capture_boundary_and_claw(void) { prepare_fixture(); set_ball(100000, 100000, 1000, 0); TdkpinCollisionRecord capture = base_record(3); capture.point1_x_milli = 103000; capture.point1_y_milli = 102000; capture.radius_milli = borland_i32_to_real48(3800); capture.contact_state = 99; capture.flags = 0x0010; write_record(50, &capture); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert(g_event_calls == 1 && g_last_event_id == 50); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) != 1000); prepare_fixture(); set_ball(100000, 100000, 0, 0); capture = base_record(3); capture.point1_x_milli = 100000; capture.point1_y_milli = 100000; capture.radius_milli = borland_i32_to_real48(20000); capture.contact_state = 1; write_record(89, &capture); win16_write_u32(g_window, 0x0bca, 300); for (uint16_t id = 12; id <= 20; id++) { TdkpinCollisionRecord gate = base_record(4); write_record(id, &gate); } tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert(win16_read_u8(win16_dgroup_pointer(0x07ca)) == 1); assert(win16_read_u16(win16_dgroup_pointer(0x086f)) == 1); assert(win16_read_u8(win16_dgroup_pointer(0x0873)) == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 1); for (uint16_t id = 12; id <= 20; id++) { assert(win16_read_u8( win16_far_add_offset(record_pointer(id), 0x34)) == 0); } } static void test_out_of_bounds_and_zero_substeps_multiball(void) { prepare_fixture(); set_ball(340000, 100000, 1001, 0); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert(g_finish_calls == 1); prepare_fixture(); win16_write_u8(win16_far_add_offset(g_window, 0x61), 2); win16_write_u16(win16_dgroup_pointer(0x0871), 0, 0); tdkpin_simulate_ball_and_dispatch_collision(0x0300); assert(g_load_calls == 2 && g_save_calls == 2); assert(g_render_calls == 2 && g_update_calls == 0); } int main(void) { test_free_integration_and_speed_clamp(); test_circle_and_segment_collisions(); test_capture_and_trigger_records(); test_capture_boundary_and_claw(); test_out_of_bounds_and_zero_substeps_multiball(); return 0; }