#include "../tdkpin_timer_tick.h" #include "../tdkpin_borland_runtime.h" #include "../tdkpin_collision_records.h" #include #include #include typedef struct { HDC16 destination; INT16 x; INT16 y; INT16 width; INT16 height; HDC16 source; INT16 source_x; INT16 source_y; uint32_t raster_operation; } BlitCall; static uint8_t g_dgroup[0x5000]; static uint8_t g_objects[0x1000]; static uint8_t g_stack[0x1000]; static Win16FarPtr g_window; static uint16_t g_refresh_ids[200]; static size_t g_refresh_count; static uint16_t g_restore_a_ids[32]; static size_t g_restore_a_id_count; static uint16_t g_restore_b_ids[32]; static size_t g_restore_b_id_count; static uint16_t g_save_slots[8]; static size_t g_save_count; static uint16_t g_load_slots[8]; static size_t g_load_count; static int32_t g_dynamic_records[8]; static size_t g_dynamic_count; static uint16_t g_sounds[8]; static size_t g_sound_count; static uint8_t g_claw_alternate; static uint16_t g_claw_frame; static unsigned g_claw_calls; static unsigned g_render_ball_calls; static unsigned g_rotate_calls; static unsigned g_restore_previous_calls; static unsigned g_finish_calls; static unsigned g_simulate_calls; static unsigned g_reset_calls; static unsigned g_flipper_calls; static unsigned g_drain_calls; static unsigned g_initialize_calls; static unsigned g_redraw_calls; static unsigned g_progress_calls; static unsigned g_panel_calls; static int16_t g_last_item; static unsigned g_item_calls; static unsigned g_region_a_calls; static unsigned g_region_b_calls; static unsigned g_present_calls; static BlitCall g_blits[16]; static size_t g_blit_count; static unsigned g_get_dc_calls; static unsigned g_release_dc_calls; static unsigned g_create_dc_calls; static unsigned g_create_bitmap_calls; static unsigned g_select_calls; static unsigned g_delete_object_calls; static unsigned g_delete_dc_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_refresh_collision_record(uint16_t caller_bp, uint16_t id) { assert(caller_bp == 0x0300); g_refresh_ids[g_refresh_count++] = id; } void tdkpin_restore_record_or_item_a(uint16_t caller_bp, uint16_t id) { assert(caller_bp == 0x0300); g_restore_a_ids[g_restore_a_id_count++] = id; } void tdkpin_restore_record_b(uint16_t caller_bp, uint16_t id) { assert(caller_bp == 0x0300); g_restore_b_ids[g_restore_b_id_count++] = id; } void tdkpin_save_ball_slot(uint16_t caller_bp, uint16_t slot) { assert(caller_bp == 0x0300); g_save_slots[g_save_count++] = slot; } void tdkpin_load_ball_slot(uint16_t caller_bp, uint16_t slot) { assert(caller_bp == 0x0300); g_load_slots[g_load_count++] = slot; } void tdkpin_update_dynamic_ball_record( uint16_t caller_bp, int32_t displacement) { assert(caller_bp == 0x0300); g_dynamic_records[g_dynamic_count++] = displacement; } void tdkpin_play_sound_if_not_tilted(uint16_t sound_number) { g_sounds[g_sound_count++] = sound_number; } void tdkpin_draw_claw_frame( uint8_t alternate, uint16_t frame, HDC16 destination) { assert(destination == 0x3333); g_claw_alternate = alternate; g_claw_frame = frame; g_claw_calls++; } void tdkpin_render_ball_from_caller_frame(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_render_ball_calls++; } void tdkpin_rotate_and_draw_targets(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_rotate_calls++; } void tdkpin_restore_previous_ball_region( uint16_t caller_bp, uint8_t suppress_present) { assert(caller_bp == 0x0300 && suppress_present == 0); g_restore_previous_calls++; } void tdkpin_finish_ball_or_advance_player(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_finish_calls++; } void tdkpin_simulate_ball_and_dispatch_collision(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_simulate_calls++; } void tdkpin_reset_ball_state(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_reset_calls++; } void tdkpin_update_flippers(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_flipper_calls++; } void tdkpin_drain_messages_except_input(void) { g_drain_calls++; } void tdkpin_initialize_player_state(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_initialize_calls++; } void tdkpin_redraw_all_players(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_redraw_calls++; } void tdkpin_refresh_player_progress_markers(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_progress_calls++; } void tdkpin_draw_panel_animation(uint16_t caller_bp) { assert(caller_bp == 0x0300); g_panel_calls++; } void tdkpin_draw_item_status(int16_t item, HDC16 destination) { assert(destination == 0x3333); g_last_item = item; g_item_calls++; } void tdkpin_restore_background_region_a( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { (void)height; (void)width; (void)y; (void)x; assert(dc == 0x3333); g_region_a_calls++; } void tdkpin_restore_background_region_b( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { (void)height; (void)width; (void)y; (void)x; assert(dc == 0x3333); g_region_b_calls++; } void tdkpin_present_background_region( INT16 height, INT16 width, INT16 y, INT16 x, HDC16 dc) { (void)height; (void)width; (void)y; (void)x; assert(dc == 0x3333); g_present_calls++; } 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; } HDC16 CreateCompatibleDC16(HDC16 dc) { assert(dc == 0x3333); g_create_dc_calls++; return g_create_dc_calls == 1 ? 0x4444 : 0x5555; } HBITMAP16 CreateCompatibleBitmap16(HDC16 dc, INT16 width, INT16 height) { assert(dc == 0x3333 && width == 30 && height == 284); g_create_bitmap_calls++; return 0x6666; } HGDIOBJ16 SelectObject16(HDC16 dc, HGDIOBJ16 object) { assert(dc == 0x4444 || dc == 0x5555); (void)object; g_select_calls++; return (HGDIOBJ16)(0x7000 + g_select_calls); } BOOL16 BitBlt16( HDC16 destination, INT16 x, INT16 y, INT16 width, INT16 height, HDC16 source, INT16 source_x, INT16 source_y, uint32_t raster_operation) { g_blits[g_blit_count++] = (BlitCall){ destination, x, y, width, height, source, source_x, source_y, raster_operation, }; return 1; } BOOL16 DeleteObject16(HGDIOBJ16 object) { assert(object == 0x6666); g_delete_object_calls++; return 1; } BOOL16 DeleteDC16(HDC16 dc) { assert(dc == 0x4444 || dc == 0x5555); g_delete_dc_calls++; return 1; } static Win16FarPtr record(uint16_t id) { return win16_dgroup_pointer((uint16_t)( 0x095b + id * sizeof(TdkpinCollisionRecord))); } 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, 0x0300); g_window = win16_make_far_pointer(0x7000, 0); win16_write_u16(g_window, 4, 0x2222); win16_write_u8(win16_dgroup_pointer(0x07c4), 1); memset(g_refresh_ids, 0, sizeof(g_refresh_ids)); memset(g_restore_a_ids, 0, sizeof(g_restore_a_ids)); memset(g_restore_b_ids, 0, sizeof(g_restore_b_ids)); g_refresh_count = g_restore_a_id_count = g_restore_b_id_count = 0; g_save_count = g_load_count = g_dynamic_count = g_sound_count = 0; g_claw_calls = g_render_ball_calls = g_rotate_calls = 0; g_restore_previous_calls = g_finish_calls = g_simulate_calls = 0; g_reset_calls = g_flipper_calls = g_drain_calls = 0; g_initialize_calls = g_redraw_calls = g_progress_calls = 0; g_panel_calls = g_item_calls = 0; g_region_a_calls = g_region_b_calls = g_present_calls = 0; g_blit_count = g_get_dc_calls = g_release_dc_calls = 0; g_create_dc_calls = g_create_bitmap_calls = g_select_calls = 0; g_delete_object_calls = g_delete_dc_calls = 0; } static void test_guard_and_refresh_epilogue(void) { prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c4), 0); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_flipper_calls == 0 && g_drain_calls == 0); prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c5), 1); win16_write_u16(g_window, 0x5e, 2); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_refresh_count == 175); assert(g_refresh_ids[0] == 1 && g_refresh_ids[174] == 175); assert(win16_read_u8(win16_dgroup_pointer(0x07c5)) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x0876)) == 1); assert(g_flipper_calls == 1 && g_drain_calls == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x51)) == 0); assert(win16_read_u16(win16_far_add_offset(g_window, 0x5e)) == 1); } static void test_claw_release(void) { prepare_fixture(); win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1); win16_write_u8(win16_dgroup_pointer(0x07ca), 1); win16_write_u16(win16_dgroup_pointer(0x086d), 0, 2); win16_write_u16(win16_dgroup_pointer(0x086f), 0, 2); win16_write_u32(g_window, 0x56, 1000); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_claw_calls == 1 && g_claw_alternate == 0 && g_claw_frame == 2); assert(win16_read_u16(win16_dgroup_pointer(0x086f)) == 10); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 259000); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 81000); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0baa)) == -450); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == 50); assert(g_save_count == 1 && g_save_slots[0] == 1); assert(g_render_ball_calls == 1); assert(g_sound_count == 1 && g_sounds[0] == 16); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 0); assert(g_simulate_calls == 1); } static void test_multiball_promotion(void) { prepare_fixture(); win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1); win16_write_u8(win16_far_add_offset(g_window, 0x61), 2); win16_write_u8(win16_far_add_offset(g_window, 0x0be1), 1); win16_write_u8(win16_dgroup_pointer(0x424c), 1); win16_write_u32(win16_dgroup_pointer(0x07e7), 0, 0x11111111); win16_write_u32(win16_dgroup_pointer(0x07ef), 0, 0x22222222); win16_write_u32(win16_dgroup_pointer(0x07f7), 0, 0x33333333); win16_write_u32(win16_dgroup_pointer(0x07ff), 0, 0x44444444); win16_write_u32(win16_dgroup_pointer(0x0807), 0, 0x55555555); for (uint16_t index = 0; index < 6; index++) { win16_write_u8( win16_dgroup_pointer((uint16_t)(0x0811 + index)), (uint8_t)(0xa0 + index)); } tdkpin_timer_tick_with_frame(0x0300, g_window); assert(win16_read_u8(win16_far_add_offset(g_window, 0x61)) == 1); assert(win16_read_u8(win16_dgroup_pointer(0x41f9)) == 1); assert(win16_read_u8(win16_dgroup_pointer(0x424c)) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x07e3)) == 0x11111111); assert(win16_read_u32(win16_dgroup_pointer(0x07eb)) == 0x22222222); assert(win16_read_u32(win16_dgroup_pointer(0x07f3)) == 0x33333333); assert(win16_read_u32(win16_dgroup_pointer(0x07fb)) == 0x44444444); assert(win16_read_u32(win16_dgroup_pointer(0x0803)) == 0x55555555); assert(g_load_count == 1 && g_load_slots[0] == 0); assert(g_finish_calls == 0); } static void test_multiball_spawn(void) { prepare_fixture(); win16_write_u8(win16_far_add_offset(g_window, 0x0bdd), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0bdf), 1); win16_write_u8(win16_far_add_offset(g_window, 0x0be0), 1); win16_write_u8(win16_dgroup_pointer(0x07d1), 1); win16_write_u32(win16_dgroup_pointer(0x3969), 0, 123000); win16_write_u32(win16_dgroup_pointer(0x396d), 0, 456000); win16_write_u32(g_window, 0x56, 1000); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_save_count == 2 && g_save_slots[0] == 0 && g_save_slots[1] == 1); assert(g_dynamic_count == 2 && g_dynamic_records[0] == 1 && g_dynamic_records[1] == 2); assert(g_restore_previous_calls == 1); assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1020); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d3)) == 123000); assert((int32_t)win16_read_u32(win16_dgroup_pointer(0x07d7)) == 456000); assert((int32_t)win16_read_u32( win16_far_add_offset(g_window, 0x0bae)) == 800); assert(win16_read_u8(win16_far_add_offset(g_window, 0x61)) == 2); assert(win16_read_u8(win16_dgroup_pointer(0x0874)) == 1); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0be0)) == 0); } static void test_intro_tick_32_and_marquee(void) { prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 31); win16_write_u16(record(90), 0x4b, 10); win16_write_u16(record(90), 0x4d, 20); win16_write_u16(record(90), 0x4f, 50); win16_write_u16(record(90), 0x51, 60); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_restore_a_id_count == 5); assert(g_restore_a_ids[0] == 140); assert(g_restore_a_ids[1] == 1001); assert(g_restore_a_ids[2] == 153 && g_restore_a_ids[3] == 154 && g_restore_a_ids[4] == 6); assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1002); assert(g_region_a_calls == 1 && g_region_b_calls == 2); assert(g_present_calls == 3); assert(g_item_calls == 1 && g_last_item == 4); prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 5); win16_write_u16(win16_dgroup_pointer(0x085f), 0, 0x7777); win16_write_u16(win16_dgroup_pointer(0x0861), 0, 0x8888); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(win16_read_u32(win16_dgroup_pointer(0x087a)) == 1); assert(g_create_dc_calls == 2 && g_create_bitmap_calls == 1); assert(g_blit_count == 4); assert(g_blits[0].destination == 0x5555 && g_blits[0].width == 30 && g_blits[0].height == 100 && g_blits[0].source_x == 473 && g_blits[0].source_y == 277); assert(g_blits[2].height == 237 && g_blits[2].source_x == 400); assert(g_blits[3].destination == 0x3333 && g_blits[3].x == 233 && g_blits[3].y == 372); assert(g_select_calls == 5 && g_delete_object_calls == 1); assert(g_delete_dc_calls == 2); static const struct { uint32_t before_tick; int16_t expected_item; } item_cases[] = { {7, 1}, {71, 9}, {79, 8}, {135, 1}, {143, 0}, }; for (size_t index = 0; index < sizeof(item_cases) / sizeof(item_cases[0]); index++) { prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32( win16_dgroup_pointer(0x0876), 0, item_cases[index].before_tick); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_item_calls == 1); assert(g_last_item == item_cases[index].expected_item); } } static void test_intro_remainder_driven_cycles(void) { prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 3); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_restore_a_id_count == 2); assert(g_restore_a_ids[0] == 142 && g_restore_a_ids[1] == 1002); assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1001); assert(g_region_a_calls == 2 && g_region_b_calls == 1); assert(g_item_calls == 0); prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 15); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_restore_a_id_count == 1 && g_restore_a_ids[0] == 1005); assert(g_restore_b_id_count == 5); assert(g_restore_b_ids[0] == 140 && g_restore_b_ids[1] == 1004); assert(g_restore_b_ids[2] == 153 && g_restore_b_ids[3] == 154 && g_restore_b_ids[4] == 6); assert(g_region_a_calls == 2 && g_region_b_calls == 1); assert(g_item_calls == 1 && g_last_item == 2); prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c9), 1); win16_write_u32(win16_dgroup_pointer(0x0876), 0, 39); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_restore_a_id_count == 2); assert(g_restore_a_ids[0] == 144 && g_restore_a_ids[1] == 1003); assert(g_restore_b_id_count == 1 && g_restore_b_ids[0] == 1002); assert(g_region_a_calls == 2 && g_region_b_calls == 1); assert(g_item_calls == 1 && g_last_item == 5); } static void test_panel_and_game_initialization(void) { prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c8), 1); win16_write_u32(win16_dgroup_pointer(0x0851), 0, 1); for (uint16_t id = 129; id <= 133; id++) { win16_write_u16(record(id), 0x43, 0xffff); } tdkpin_timer_tick_with_frame(0x0300, g_window); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 1); assert(win16_read_u32(win16_dgroup_pointer(0x0851)) == 2); assert(g_panel_calls == 1); for (uint16_t id = 129; id <= 133; id++) { assert(win16_read_u16(win16_far_add_offset(record(id), 0x43)) == 0); } prepare_fixture(); win16_write_u8(win16_dgroup_pointer(0x07c8), 1); win16_write_u32(win16_dgroup_pointer(0x0851), 0, 281); win16_write_u8(win16_dgroup_pointer(0x07d1), 1); for (uint16_t item = 15; item <= 19; item++) { win16_write_u8( win16_far_add_offset(g_window, (uint16_t)(0x47b + 20 + item)), 1); } tdkpin_timer_tick_with_frame(0x0300, g_window); assert(win16_read_u8(win16_dgroup_pointer(0x07c8)) == 0); assert(win16_read_u8(win16_far_add_offset(g_window, 0x0bdf)) == 0); assert(win16_read_u32(win16_dgroup_pointer(0x0851)) == 0); assert(g_region_b_calls == 1 && g_present_calls == 1); prepare_fixture(); win16_write_u16(win16_dgroup_pointer(0x081f), 0, 0xffff); tdkpin_timer_tick_with_frame(0x0300, g_window); assert(g_initialize_calls == 1 && g_redraw_calls == 1); assert(g_progress_calls == 1 && g_reset_calls == 1); assert(g_region_b_calls == 1 && g_present_calls == 1); assert(win16_read_u32(win16_dgroup_pointer(0x0876)) == 0); } int main(void) { test_guard_and_refresh_epilogue(); test_claw_release(); test_multiball_promotion(); test_multiball_spawn(); test_intro_tick_32_and_marquee(); test_intro_remainder_driven_cycles(); test_panel_and_game_initialization(); return 0; }