361c9d9f78
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь, пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) — решётка (0,3) открывается — и убегает. Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты (images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама. - guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в autocontrol_opponent / play_guard / check_can_guard_see_kid; - pop_leveldoor_open стал word, как в оригинале (в него же считается задержка — байт переполнился бы через 255 кадров); - leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5); - полосы HP у мыши нет (seg000:1159); - tests-host/t_mouse: 17 проверок, единственный набор с guards.c. Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level, find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в roomtest_cold.c (--bank 8, n_banks = 8). _CODE 25653 -> 24825, куча -> 1707 Б; кадровый путь не тронут. Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась, нажала кнопку, решётка поехала вверх, мышь ушла). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
179 lines
7.0 KiB
C
179 lines
7.0 KiB
C
/*
|
|
* 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 <stdint.h>
|
|
|
|
#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);
|
|
}
|