#include "../tdkpin_key_input.h" #include "../tdkpin_random.h" #include "../tdkpin_real48.h" #include #include static uint8_t g_dgroup[0x5000]; static uint8_t g_objects[0x2000]; static Win16FarPtr g_window; static Win16FarPtr g_event; static uint16_t g_random_values[16]; static uint16_t g_random_bounds[16]; static size_t g_random_count; static size_t g_random_index; static uint16_t g_sounds[8]; static size_t g_sound_count; static unsigned g_get_dc_calls; static unsigned g_release_dc_calls; static unsigned g_blit_calls; static unsigned g_restore_a_calls; static unsigned g_restore_b_calls; static unsigned g_present_calls; static unsigned g_draw_item_calls; static unsigned g_draw_score_calls; static unsigned g_status_calls; typedef enum { REAL_MODE_NONE, REAL_MODE_UPPER_CLAMP, REAL_MODE_HORIZONTAL_NUDGE, } RealMode; static RealMode g_real_mode; static unsigned g_real_multiply_calls; static const BorlandReal48 g_real_random = {{0x80, 1, 2, 3, 4, 5}}; static const BorlandReal48 g_real_product = {{0x81, 6, 7, 8, 9, 10}}; static const BorlandReal48 g_real_factor = {{0x82, 11, 12, 13, 14, 15}}; static const BorlandReal48 g_real_integer = {{0x83, 16, 17, 18, 19, 20}}; static const BorlandReal48 g_real_result = {{0x84, 21, 22, 23, 24, 25}}; static void assert_real48(BorlandReal48 actual, BorlandReal48 expected) { assert(memcmp(actual.bytes, expected.bytes, sizeof(actual.bytes)) == 0); } int32_t borland_multiply_i32(int32_t left, int32_t right) { return (int32_t)((uint32_t)left * (uint32_t)right); } uint16_t borland_random_below(uint16_t upper_bound) { assert(g_random_index < g_random_count); assert(upper_bound == g_random_bounds[g_random_index]); return g_random_values[g_random_index++]; } BorlandReal48 borland_random_real48_registers(void) { assert(g_real_mode == REAL_MODE_HORIZONTAL_NUDGE); return g_real_random; } BorlandReal48 borland_i32_to_real48(int32_t value) { if (g_real_mode == REAL_MODE_UPPER_CLAMP) { assert(value == 1000); } else { assert(g_real_mode == REAL_MODE_HORIZONTAL_NUDGE); assert(value == 3); } return g_real_integer; } BorlandReal48 borland_real48_multiply( BorlandReal48 left, BorlandReal48 right) { static const BorlandReal48 three_fifths = {{0x80, 0x9a, 0x99, 0x99, 0x99, 0x19}}; static const BorlandReal48 nudge_scale = {{0x85, 0, 0, 0, 0, 0x20}}; if (g_real_mode == REAL_MODE_UPPER_CLAMP) { assert(g_real_multiply_calls++ == 0); assert_real48(left, g_real_integer); assert_real48(right, three_fifths); return g_real_result; } assert(g_real_mode == REAL_MODE_HORIZONTAL_NUDGE); if (g_real_multiply_calls++ == 0) { assert_real48(left, g_real_random); assert_real48(right, nudge_scale); return g_real_product; } assert_real48(left, g_real_integer); assert_real48(right, g_real_factor); return g_real_result; } BorlandReal48 borland_real48_subtract( BorlandReal48 left, BorlandReal48 right) { static const BorlandReal48 nudge_offset = {{0x84, 0, 0, 0, 0, 0x20}}; assert(g_real_mode == REAL_MODE_HORIZONTAL_NUDGE); assert_real48(left, g_real_product); assert_real48(right, nudge_offset); return g_real_factor; } int32_t borland_real48_round_to_i32(BorlandReal48 value) { assert_real48(value, g_real_result); return g_real_mode == REAL_MODE_UPPER_CLAMP ? 600 : 7; } 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; } HDC16 GetDC16(HWND16 window) { assert(window == 0x2222); g_get_dc_calls++; return 0x3333; } INT16 ReleaseDC16(HWND16 window, HDC16 dc) { assert(window == 0x2222 && dc == 0x3333); g_release_dc_calls++; return 1; } void tdkpin_blit_bitmap_and_present( HBITMAP16 bitmap, INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { assert(bitmap == 0x4444); assert(height == 18 && width == 21); assert(y == 426 && x == 314 && dc == 0x3333); g_blit_calls++; } void tdkpin_restore_background_region_a( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { assert(dc == 0x3333); if (height == 20) { assert(width == 148 && y == 243 && x == 84); } else { assert(height == 30 && width == 284 && y == 232 && x == 371); } g_restore_a_calls++; } void tdkpin_restore_background_region_b( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { assert(height == 42 && width == 284 && x == 348 && dc == 0x3333); assert(y == 360 || y == 405); g_restore_b_calls++; } void tdkpin_present_background_region( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { assert(dc == 0x3333); if (height == 20) { assert(width == 148 && y == 243 && x == 84); } else if (height == 30) { assert(width == 284 && y == 232 && x == 371); } else { assert(height == 42 && width == 284 && x == 348); assert(y == 360 || y == 405); } g_present_calls++; } void tdkpin_draw_item_status(int16_t item, HDC16 dc) { assert(item == 0 && dc == 0x3333); g_draw_item_calls++; } void tdkpin_draw_score(HDC16 dc, int32_t value_or_player) { assert(dc == 0x3333 && value_or_player == 0); g_draw_score_calls++; } void tdkpin_update_status_display(uint8_t player, HDC16 dc) { assert(player == 1 && dc == 0x3333); g_status_calls++; } static void set_random_script( const uint16_t *bounds, const uint16_t *values, size_t count) { assert(count <= sizeof(g_random_values) / sizeof(g_random_values[0])); memcpy(g_random_bounds, bounds, count * sizeof(bounds[0])); memcpy(g_random_values, values, count * sizeof(values[0])); g_random_count = count; g_random_index = 0; } static void prepare_fixture(uint16_t scan_code) { memset(g_dgroup, 0, sizeof(g_dgroup)); memset(g_objects, 0, sizeof(g_objects)); 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_set_dgroup_selector(0x7200); g_window = win16_make_far_pointer(0x7000, 0); g_event = win16_make_far_pointer(0x7000, 0x1000); win16_write_u16(g_window, 4, 0x2222); win16_write_u16(g_event, 8, scan_code); win16_write_u16(g_event, 0x0a, 0xaaaa); win16_write_u16(g_event, 0x0c, 0xbbbb); win16_write_u16(win16_dgroup_pointer(0x0865), 0, 0x4444); g_random_count = 0; g_random_index = 0; g_sound_count = 0; g_get_dc_calls = 0; g_release_dc_calls = 0; g_blit_calls = 0; g_restore_a_calls = 0; g_restore_b_calls = 0; g_present_calls = 0; g_draw_item_calls = 0; g_draw_score_calls = 0; g_status_calls = 0; g_real_mode = REAL_MODE_NONE; g_real_multiply_calls = 0; } static void assert_event_consumed(void) { assert(win16_read_u16(win16_far_add_offset(g_event, 0x0a)) == 0); assert(win16_read_u16(win16_far_add_offset(g_event, 0x0c)) == 0); } static void test_debug_keys_and_auxiliary_gate(void) { prepare_fixture(0x3c); win16_write_u8(win16_dgroup_pointer(0x07cf), 1); win16_write_u32(win16_dgroup_pointer(0x0855), 0, 0x12345678); assert(tdkpin_handle_key_release(g_window, g_event) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x0855)) == 0); assert(win16_read_u8(win16_dgroup_pointer(0x07c7)) == 1); assert_event_consumed(); prepare_fixture(0x41); win16_write_u8(win16_dgroup_pointer(0x07cf), 1); win16_write_u32(win16_dgroup_pointer(0x3340), 0, 0x12345678); win16_write_u32(win16_dgroup_pointer(0x3344), 0, 0x90abcdef); win16_write_u32(g_window, 0x0baa, 1); win16_write_u32(g_window, 0x0bae, 2); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u32(win16_dgroup_pointer(0x07d3)) == 0x12345678); assert(win16_read_u32(win16_dgroup_pointer(0x07d7)) == 0x90abcdef); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0baa)) == 0); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0bae)) == 0); prepare_fixture(0x3c); win16_write_u8(win16_dgroup_pointer(0x07cf), 1); win16_write_u16(g_window, 0x41, 1); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_dgroup_pointer(0x07c7)) == 0); assert_event_consumed(); } static void test_release_down_impulse_clamps(void) { static const uint16_t lower_bounds[] = {1000}; static const uint16_t lower_values[] = {80}; prepare_fixture(0x50); win16_write_u8(win16_dgroup_pointer(0x07d0), 1); win16_write_u32(g_window, 0x56, 1000); win16_write_u32(g_window, 0x5a, 2); win16_write_u32(g_window, 0x0baa, 0x12345678); win16_write_u32(g_window, 0x0bae, (uint32_t)-2000); set_random_script(lower_bounds, lower_values, 1); tdkpin_handle_key_release(g_window, g_event); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == -998); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0baa)) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x07fb)) == (uint32_t)-998); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdd)) == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bde)) == 0); assert(g_random_index == 1); assert(g_sound_count == 1 && g_sounds[0] == 2); assert(g_blit_calls == 1 && g_get_dc_calls == 1 && g_release_dc_calls == 1); static const uint16_t upper_bounds[] = {1000}; static const uint16_t upper_values[] = {80}; prepare_fixture(0x50); win16_write_u8(win16_dgroup_pointer(0x07d0), 1); win16_write_u32(g_window, 0x56, 1000); win16_write_u32(g_window, 0x5a, 2); win16_write_u32(g_window, 0x0bae, (uint32_t)-399); g_real_mode = REAL_MODE_UPPER_CLAMP; set_random_script(upper_bounds, upper_values, 1); tdkpin_handle_key_release(g_window, g_event); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == -601); assert(g_real_multiply_calls == 1 && g_random_index == 1); } static void test_flipper_key_release(void) { prepare_fixture(0x1d); win16_write_u8(win16_far_add_offset(g_window, 0x0bda), 1); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bda)) == 0); prepare_fixture(0x1c); win16_write_u8(win16_far_add_offset(g_window, 0x0bdb), 1); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdb)) == 0); } static void test_directional_and_multiball_nudges(void) { static const uint16_t left_bounds[] = {20, 10}; static const uint16_t left_values[] = {10, 9}; prepare_fixture(0x2a); win16_write_u32(g_window, 0x5a, 3); win16_write_u32(g_window, 0x0baa, 1000); set_random_script(left_bounds, left_values, 2); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0baa)) == 880); assert(win16_read_u16(win16_far_add_offset(g_window, 0x5e)) == 25); assert(g_sound_count == 1 && g_sounds[0] == 19); static const uint16_t right_bounds[] = {20, 10}; static const uint16_t right_values[] = {10, 9}; prepare_fixture(0x51); win16_write_u32(g_window, 0x5a, 3); win16_write_u8(win16_far_add_offset(g_window, 0x61), 2); win16_write_u32(win16_dgroup_pointer(0x07f3), 0, 1000); win16_write_u32(win16_dgroup_pointer(0x07f7), 0, 2000); set_random_script(right_bounds, right_values, 2); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0baa)) == 120); assert(win16_read_u32(win16_dgroup_pointer(0x07f3)) == 1120); assert(win16_read_u32(win16_dgroup_pointer(0x07f7)) == 2120); static const uint16_t space_bounds[] = {20, 21, 100, 10}; static const uint16_t space_values[] = {10, 12, 20, 9}; prepare_fixture(0x39); win16_write_u32(g_window, 0x5a, 3); win16_write_u8(win16_far_add_offset(g_window, 0x61), 2); win16_write_u32(g_window, 0x0baa, 100); win16_write_u32(g_window, 0x0bae, 200); win16_write_u32(win16_dgroup_pointer(0x07f3), 0, 1000); win16_write_u32(win16_dgroup_pointer(0x07f7), 0, 2000); win16_write_u32(win16_dgroup_pointer(0x07fb), 0, 3000); win16_write_u32(win16_dgroup_pointer(0x07ff), 0, 4000); g_real_mode = REAL_MODE_HORIZONTAL_NUDGE; set_random_script(space_bounds, space_values, 4); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u32(win16_far_add_offset(g_window, 0x0baa)) == 107); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == 50); assert(win16_read_u32(win16_dgroup_pointer(0x07f3)) == 1006); assert(win16_read_u32(win16_dgroup_pointer(0x07f7)) == 2006); assert(win16_read_u32(win16_dgroup_pointer(0x07fb)) == 2790); assert(win16_read_u32(win16_dgroup_pointer(0x07ff)) == 3790); assert(g_real_multiply_calls == 2 && g_random_index == 4); } static void test_tilt_threshold(void) { static const uint16_t bounds[] = {20, 10}; static const uint16_t values[] = {10, 0}; prepare_fixture(0x2a); win16_write_u32(g_window, 0x5a, 1); win16_write_u16(g_window, 0x5e, 14); win16_write_u8(win16_far_add_offset(g_window, 0x0bda), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0bdb), 1); set_random_script(bounds, values, 2); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u16(win16_far_add_offset(g_window, 0x5e)) == 39); assert(win16_read_u8(win16_dgroup_pointer(0x07c6)) == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bda)) == 0); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdb)) == 0); assert(g_sound_count == 2 && g_sounds[0] == 19 && g_sounds[1] == 20); assert(g_restore_a_calls == 1 && g_present_calls == 1); assert(g_get_dc_calls == 1 && g_release_dc_calls == 1); } static void test_sound_toggle(void) { prepare_fixture(0x58); win16_write_u16(win16_dgroup_pointer(0x43ee), 0, 1); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_dgroup_pointer(0x43f2)) == 1); prepare_fixture(0x58); win16_write_u8(win16_dgroup_pointer(0x43f2), 1); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_dgroup_pointer(0x43f2)) == 0); prepare_fixture(0x58); tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_dgroup_pointer(0x43f2)) == 0); } static void test_add_player_copies_all_state(void) { prepare_fixture(0x4e); win16_write_u8(win16_far_add_offset(g_window, 0x0bde), 1); win16_write_u8(win16_dgroup_pointer(0x07d0), 1); win16_write_u32(win16_dgroup_pointer(0x0982), 0, 0x11111111); win16_write_u32(win16_dgroup_pointer(0x0986), 0, 0x22222222); win16_write_u32(win16_dgroup_pointer(0x098a), 0, 0x33333333); win16_write_u32(win16_dgroup_pointer(0x098e), 0, 0x44444444); for (uint16_t id = 1; id <= 175; id++) { win16_write_u8( win16_far_add_offset(g_window, (uint16_t)(0x03cc + id)), (uint8_t)(id ^ 0x5a)); win16_write_u16( g_window, (uint16_t)(0x0a42 + id * 2u), (uint16_t)(id * 257u)); } tdkpin_handle_key_release(g_window, g_event); assert(win16_read_u8(win16_dgroup_pointer(0x07d0)) == 2); assert(win16_read_u8(win16_dgroup_pointer(0x07c9)) == 0); assert(win16_read_u8(win16_dgroup_pointer(0x07c5)) == 1); assert(win16_read_u8(win16_dgroup_pointer(0x0874)) == 0); assert(win16_read_u8(win16_dgroup_pointer(0x07d1)) == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x61)) == 1); assert(g_sound_count == 1 && g_sounds[0] == 1); for (uint16_t player = 1; player <= 4; player++) { assert(win16_read_u16(win16_dgroup_pointer( (uint16_t)(0x081f + player * 2u))) == 0xffff); for (uint16_t id = 1; id <= 175; id++) { uint8_t expected_byte = (uint8_t)(id ^ 0x5a); uint16_t expected_word = (uint16_t)(id * 257u); assert(win16_read_u8(win16_far_add_offset( g_window, (uint16_t)(0x0061 + player * 0x00afu + id))) == expected_byte); assert(win16_read_u16(win16_far_add_offset( g_window, (uint16_t)(0x036c + player * 0x015eu + id * 2u))) == expected_word); } } for (uint16_t id = 1; id <= 175; id++) { Win16FarPtr record = win16_dgroup_pointer( (uint16_t)(0x095b + id * 0x53u)); assert(win16_read_u8(win16_far_add_offset(record, 0x34)) == (uint8_t)(id ^ 0x5a)); assert(win16_read_u16(win16_far_add_offset(record, 0x43)) == (uint16_t)(id * 257u)); } assert(win16_read_u32(win16_dgroup_pointer(0x0982)) == 0x11111111); assert(win16_read_u32(win16_dgroup_pointer(0x0986)) == 0x22222222); assert(win16_read_u32(win16_dgroup_pointer(0x098a)) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x098e)) == 0); assert(g_restore_b_calls == 2 && g_restore_a_calls == 1); assert(g_present_calls == 3 && g_draw_item_calls == 4); assert(g_draw_score_calls == 1 && g_status_calls == 1); assert(g_get_dc_calls == 5 && g_release_dc_calls == 5); } int main(void) { test_debug_keys_and_auxiliary_gate(); test_release_down_impulse_clamps(); test_flipper_key_release(); test_directional_and_multiball_nudges(); test_tilt_threshold(); test_sound_toggle(); test_add_player_copies_all_state(); return 0; }