/* * t_mouse — спецсобытие уровня 8: МЫШЬ (do_timers seg003:0545, * do_mouse seg003:0ABA, autocontrol_mouse seg002:07EB). * * Единственный набор, куда линкуется guards.c: проверяется не физика (её * держат t_phys/t_char), а связка «условие запуска → рождение → ИИ → * нажатие кнопки → уход». Всё это ветки по charid внутри общего кода * соперника, и сломать их можно, ничего не трогая в самой мыши. * * Кадр соперника здесь полный, с ИИ — pop_guard_tick() + pop_guard_phys_tick(), * как в главном цикле; sc_guard_run для этого не годится (он намеренно без ИИ). */ #include #include "tcheck.h" #include "pop_kid.h" #include "pop_map.h" #include "pop_guard.h" #include "pop_geom.h" #include "pop_state.h" #include "pop_level.h" /* pop_current_level (под тестом — из stubs.c) */ #include "pop_tune.h" #include "scene.h" #include "stubs.h" #define E 0 /* empty */ #define F 1 /* floor */ #define O 15 /* opener */ #define W 20 /* wall */ /* Верхний ряд комнаты 16 в упрощённом виде: сплошной пол и кнопка на * колонке 7 — там же, где она стоит в данных уровня 8. Провал колонок * 4..6 намеренно засыпан: он мешает Киду, а не мыши, и в тест на ИИ * притащил бы падение соперника. */ static const uint8_t room_mouse[30] = { F, F, F, F, F, F, F, O, F, F, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, W, }; /* Поставить сцену уровня 8: Кид в комнате 16, дверь уровня открыта. */ static void scene_level8(void) { sc_room(room_mouse, POP_MOUSE_ROOM); sc_kid_at(1, 0, 1); Kid.room = POP_MOUSE_ROOM; pop_guard_reset(); pop_current_level = POP_MOUSE_LEVEL; pop_leveldoor_open = 1; tk_log_clear(); } /* Один полный кадр соперника, как в главном цикле roomtest.c. */ static void mouse_frame(void) { pop_guard_tick(); pop_guard_phys_tick(); } /* ---- рождение -------------------------------------------------------- */ TC_TEST(mouse_appears_after_delay) { uint16_t i; scene_level8(); /* Счётчик стартует с 1 («дверь открыта») и растёт по кадру, срабатывая * на значении POP_MOUSE_DELAY — то есть кадров до мыши на два меньше. * Прогоняем всё, кроме последнего: слот обязан быть ещё пуст. */ for (i = 0; i < POP_MOUSE_DELAY - 2; i++) pop_check_mouse(); TC_EQ(Guard.charid, 0); pop_check_mouse(); /* тот самый кадр */ TC_EQ(Guard.charid, CHARID_24_MOUSE); TC_EQ(Guard.x, POP_MOUSE_START_X); TC_EQ(Guard.curr_row, 0); TC_EQ(Guard.direction, -1); /* dir_FF_left */ TC_EQ(guardhp_curr, 1); TC_EQ(Guard.room, POP_MOUSE_ROOM); /* унаследована от Кида (loadkid) */ } /* Счётчик тикает ТОЛЬКО пока Кид в комнате мыши: в оригинале это то же * условие, что и запуск (Char.room == mouse_room), поэтому 12,5 секунды * отсчитываются с возвращения игрока, а не с открытия двери. */ TC_TEST(mouse_timer_frozen_outside_room) { uint16_t i; scene_level8(); Kid.room = POP_MOUSE_ROOM + 1; for (i = 0; i < POP_MOUSE_DELAY * 2; i++) pop_check_mouse(); TC_EQ(Guard.charid, 0); TC_EQ(pop_leveldoor_open, 1); /* не сдвинулся ни на кадр */ } /* Закрытая дверь уровня — мыши нет вовсе (она подсказка на обратном пути). */ TC_TEST(mouse_needs_open_leveldoor) { uint16_t i; scene_level8(); pop_leveldoor_open = 0; for (i = 0; i < POP_MOUSE_DELAY * 2; i++) pop_check_mouse(); TC_EQ(Guard.charid, 0); } /* ---- ИИ: пробежала, нажала, ушла ------------------------------------- */ /* Родить мышь, не гоняя POP_MOUSE_DELAY пустых кадров. */ static void spawn_mouse(void) { scene_level8(); pop_leveldoor_open = POP_MOUSE_DELAY - 1; pop_check_mouse(); } TC_TEST(mouse_runs_left_and_presses_button) { uint8_t i; spawn_mouse(); TC_EQ(Guard.charid, CHARID_24_MOUSE); /* Бежит влево: до порога разворота ей ~35 пикселей по 3-5 за кадр. */ for (i = 0; i < 24; i++) mouse_frame(); TC_TRUE(Guard.x < POP_MOUSE_START_X); /* По дороге наступила на кнопку (0,7) — общий check_press физики * соперника, тот же, которым кнопки жмут страж и тень. */ TC_TRUE(tk_log_count(TK_BUTTON) >= 1); } TC_TEST(mouse_turns_back_and_disappears) { uint8_t i; spawn_mouse(); /* 120 кадров с запасом покрывают путь туда (200→166), стойку seq_107 * (11 кадров) и путь обратно (166→200). */ for (i = 0; i < 120; i++) { mouse_frame(); if (Guard.direction == DIR_56_NONE) break; } TC_EQ(Guard.direction, DIR_56_NONE); /* clear_char: ушла со сцены */ TC_EQ(guardhp_curr, 0); } /* ---- ветки по charid у соседнего кода -------------------------------- */ /* Мышь Кида «не видит»: иначе он выхватит на неё меч (seg003:0965). */ TC_TEST(mouse_is_not_seen_by_kid) { spawn_mouse(); Guard.curr_row = Kid.curr_row; /* один ряд — условие луча */ pop_check_can_guard_see_kid(); TC_EQ(can_guard_see_kid, 0); } /* leave_guard мышь не записывает в данные уровня (seg002:02F5): вернувшись * в комнату, игрок не должен встретить «стража» с её параметрами. * Комнату берём из диапазона таблицы заглушки (TK_ROOMS = 8). */ TC_TEST(mouse_is_not_saved_as_room_guard) { spawn_mouse(); Guard.room = 1; tk_gs_tile[0] = 0xEE; /* метка «никто сюда не писал» */ pop_guard_leave(); TC_EQ(tk_gs_tile[0], 0xEE); } void main(void) { sc_init(); TC_RUN(mouse_appears_after_delay); TC_RUN(mouse_timer_frozen_outside_room); TC_RUN(mouse_needs_open_leveldoor); TC_RUN(mouse_runs_left_and_presses_button); TC_RUN(mouse_turns_back_and_disappears); TC_RUN(mouse_is_not_seen_by_kid); TC_RUN(mouse_is_not_saved_as_room_guard); }