Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006: - check_shadow: подъём тени в комнате 15 по условию «меч подобран», init_shad_12, вход падением (seq 7); - autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ; - общий урон (ранил тень — ранил себя) и check_killed_shadow (убил тень — убил себя); - таймер вспышки слияния 42 -> -1, sword_disappears при уходе из комнаты 18, появление плит в комнатах 2/13 после слияния. Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт сбросить HP. Чит навигации по комнатам этот триггер придерживает — иначе комнату 23 двенадцатого уровня не посмотреть в принципе. Уровень 13 (Джафар): guard_notice_timer (фора после встречи), on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три исключения loose-механики. Плюс спрайты визиря VIZIER.DAT — без них он рисовался обычным стражем; заодно цвет из данных комнаты применяется только к обычному стражу, как в оригинале. Падающие плиты — семь дефектов, найденных прогонами: - MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние терялись без щебня; - одиночные сигналы посадки/провала больше не затираются в кадре; - честный loose_fall: сбитая плита рождается в том же кадре, от места удара, с половинной скоростью; - при снятии плиты метится и сосед справа (висел передний торец); - heal и отрисовка кусков разнесены на два прохода; - куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y (compare_curr_objs для пары 0x80 сортирует наоборот); - wall_pattern убран из ceil_over_kid_tile: у оригинала узор из draw_tile_bottom идёт в фон, а не в передний слой. Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44). TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам. Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами тени при слиянии, чит навигации не запускает бесшовный переход, чит K убивает через штатный путь смерти. Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER, GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,12 @@ OBJS_char := $(OBJS_phys)
|
||||
# (pop_check_mouse) и общей физикой соперника — то есть ровно то, что в
|
||||
# остальных наборах заменено ручным прогоном play_guard_frame.
|
||||
OBJS_mouse := $(OBJS_phys) build/eng_guards.rel
|
||||
# t_shadow — спецсобытия уровня 12 (встреча с тенью, общий урон, слияние).
|
||||
# guards.c нужен по той же причине, что и мыши: check_shadow/
|
||||
# check_killed_shadow/sword_disappears живут в нём.
|
||||
OBJS_shadow := $(OBJS_phys) build/eng_guards.rel
|
||||
# t_jaffar — спецсобытия уровня 13 (встреча, победа, выход, падающая гряда).
|
||||
OBJS_jaffar := $(OBJS_phys) build/eng_guards.rel
|
||||
|
||||
include $(TESTKIT)/host-tests.mk
|
||||
|
||||
|
||||
@@ -46,12 +46,18 @@ void sc_room(const uint8_t *fg, uint8_t room)
|
||||
void sc_kid_at(uint8_t col, uint8_t row, int8_t dir)
|
||||
{
|
||||
kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir);
|
||||
/* kid_init комнату не трогает (в продукте её ставит enter_room_side), а
|
||||
* сцена обязана быть согласованной: логика, спрашивающая «персонаж в
|
||||
* ОТРИСОВАННОЙ комнате?» (pop_gate_over_char), иначе всегда отвечает
|
||||
* «нет». Тест, которому нужна другая комната, задаёт её сам. */
|
||||
Kid.room = tk_start_room;
|
||||
pop_kid_hp_reset();
|
||||
}
|
||||
|
||||
void sc_kid_at_x(uint8_t col, uint8_t row, uint8_t x, int8_t dir)
|
||||
{
|
||||
kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir);
|
||||
Kid.room = tk_start_room;
|
||||
Kid.x = x; /* тайл задаёт ряд, X — фазу внутри тайла */
|
||||
/* Колонку пересчитываем В ОКНЕ Char: determine_col — часть физики, а та
|
||||
* работает с активным персонажем (порт load_fram_det_col). */
|
||||
|
||||
@@ -223,12 +223,12 @@ uint8_t pop_level_guard_hp(uint8_t level) __banked { (void)level; return 3; }
|
||||
/* Тайлы «уровня» для соседних комнат: pop_map_set_edges забирает fg соседей
|
||||
* слева/справа сам (кэш на входе в комнату), значит тесту нужен источник.
|
||||
* Заполняет сам тест — по умолчанию комнат нет и всё стена. */
|
||||
uint8_t tk_room_fg[TK_ROOMS][30];
|
||||
uint8_t tk_room_fg[TK_ROOMS + 1][30]; /* индекс = НОМЕР комнаты (1..TK_ROOMS) */
|
||||
void pop_level_access_begin(void) { }
|
||||
void pop_level_access_end(void) { }
|
||||
uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos)
|
||||
{
|
||||
if (room == 0 || room >= TK_ROOMS || tilepos >= 30) return 20; /* стена */
|
||||
if (room == 0 || room > TK_ROOMS || tilepos >= 30) return 20; /* стена */
|
||||
return tk_room_fg[room][tilepos];
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ uint8_t pop_level_tile(uint8_t room, uint8_t tilepos)
|
||||
* пор, как do_pickup правит страницу уровня сразу (BUG-POTION-STRIPE). */
|
||||
void pop_level_set_tile(uint8_t room, uint8_t tilepos, uint8_t tile)
|
||||
{
|
||||
if (room == 0 || room >= TK_ROOMS || tilepos >= 30) return;
|
||||
if (room == 0 || room > TK_ROOMS || tilepos >= 30) return;
|
||||
tk_room_fg[room][tilepos] = tile;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ uint8_t tk_log_count(uint8_t kind);
|
||||
|
||||
|
||||
/* Состояние стражей по комнатам для follow_guard (см. stubs.c). */
|
||||
#define TK_ROOMS 8
|
||||
/* Комнат в таблице «уровня». 24 = столько же, сколько в настоящем уровне
|
||||
* PoP: спецсобытия поздних уровней адресуют комнаты по их РЕАЛЬНЫМ номерам
|
||||
* (меч тени лежит в 15-й, уход за ним — из 18-й), и обрезать их до восьми
|
||||
* значило бы тестировать не тот адрес. Цена — 720 байт тестового ОЗУ. */
|
||||
#define TK_ROOMS 24
|
||||
extern uint8_t tk_guard_room; /* комната, где страж есть «по данным уровня» */
|
||||
extern uint8_t tk_gs_tile[TK_ROOMS];
|
||||
extern uint16_t tk_gs_seq[TK_ROOMS];
|
||||
@@ -52,6 +56,6 @@ void tk_guard_state_clear(void);
|
||||
extern uint8_t tk_link_down;
|
||||
|
||||
/* fg комнат «уровня» — источник для кэша соседей в pop_map_set_edges. */
|
||||
extern uint8_t tk_room_fg[TK_ROOMS][30];
|
||||
extern uint8_t tk_room_fg[TK_ROOMS + 1][30]; /* индекс = номер комнаты */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#define E 0 /* empty */
|
||||
#define F 1 /* floor */
|
||||
#define G 4 /* gate (portcullis) */
|
||||
#define W 20 /* wall */
|
||||
|
||||
/* Ровный пол на ряду 1 (как room_flat в t_phys). */
|
||||
@@ -47,6 +48,13 @@ static const uint8_t room_drop2[30] = {
|
||||
W, F, F, F, F, F, F, F, F, F,
|
||||
};
|
||||
|
||||
/* Решётка ворот в (2,6) — сцена под окклюзию персонажа, стоящего в проёме. */
|
||||
static const uint8_t room_gate[30] = {
|
||||
W, E, E, E, E, E, E, E, E, E,
|
||||
W, E, E, E, E, E, E, E, E, E,
|
||||
W, F, F, F, F, F, G, F, F, F,
|
||||
};
|
||||
|
||||
/* ---- take_hp: кому достаётся урон ----------------------------------- */
|
||||
|
||||
TC_TEST(char_take_hp_goes_to_active)
|
||||
@@ -434,6 +442,57 @@ TC_TEST(char_skeleton_falls_out_elsewhere)
|
||||
tc_check(Guard.charid, 0, "скелет убран, как обычный выпавший", __LINE__);
|
||||
}
|
||||
|
||||
/* ---- Окклюзия воротами (GATE-FORE-KID) ------------------------------ *
|
||||
* Персонаж, стоящий В ПРОЁМЕ ворот, обязан уходить ЗА решётку: оригинал
|
||||
* кладёт её бары в foretable (draw_tile_fore, seg008:0D15). Решение —
|
||||
* pop_gate_over_char; сама отрисовка живёт в слое фона и в харнесс не
|
||||
* линкуется, поэтому проверяем именно РЕШЕНИЕ. */
|
||||
|
||||
TC_TEST(char_gate_covers_char_standing_in_it)
|
||||
{
|
||||
sc_room(room_gate, 1);
|
||||
sc_kid_at(6, 2, 0); /* ровно на тайле ворот */
|
||||
pop_loadkid();
|
||||
TC_TRUE(pop_gate_over_char(&Char));
|
||||
}
|
||||
|
||||
TC_TEST(char_gate_ignores_neighbour_tiles)
|
||||
{
|
||||
sc_room(room_gate, 1);
|
||||
sc_kid_at(5, 2, 0); /* колонка слева от ворот */
|
||||
pop_loadkid();
|
||||
TC_EQ(pop_gate_over_char(&Char), 0);
|
||||
sc_kid_at(7, 2, 0); /* и справа */
|
||||
pop_loadkid();
|
||||
TC_EQ(pop_gate_over_char(&Char), 0);
|
||||
}
|
||||
|
||||
TC_TEST(char_gate_ignores_other_room)
|
||||
{
|
||||
/* Персонаж той же клетки, но ДРУГОЙ комнаты (страж, оставшийся в
|
||||
* соседней): его решётка текущей комнаты не касается. */
|
||||
sc_room(room_gate, 1);
|
||||
sc_kid_at(6, 2, 0);
|
||||
pop_loadkid();
|
||||
Char.room = 5;
|
||||
TC_EQ(pop_gate_over_char(&Char), 0);
|
||||
}
|
||||
|
||||
TC_TEST(char_gate_never_in_last_column)
|
||||
{
|
||||
/* Колонка 9: бары легли бы в колонку 10, то есть в СОСЕДНЮЮ комнату —
|
||||
* у оригинала это отсекает `Kid.room != room_R`. */
|
||||
static const uint8_t room_gate9[30] = {
|
||||
W, E, E, E, E, E, E, E, E, E,
|
||||
W, E, E, E, E, E, E, E, E, E,
|
||||
W, F, F, F, F, F, F, F, F, G,
|
||||
};
|
||||
sc_room(room_gate9, 1);
|
||||
sc_kid_at(9, 2, 0);
|
||||
pop_loadkid();
|
||||
TC_EQ(pop_gate_over_char(&Char), 0);
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
sc_init();
|
||||
@@ -457,4 +516,8 @@ void main(void)
|
||||
TC_RUN(char_tiles_resolve_across_rooms);
|
||||
TC_RUN(char_skeleton_reappears_below);
|
||||
TC_RUN(char_skeleton_falls_out_elsewhere);
|
||||
TC_RUN(char_gate_covers_char_standing_in_it);
|
||||
TC_RUN(char_gate_ignores_neighbour_tiles);
|
||||
TC_RUN(char_gate_ignores_other_room);
|
||||
TC_RUN(char_gate_never_in_last_column);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* t_jaffar — спецсобытия УРОВНЯ 13: встреча с Джафаром, победа, выход и
|
||||
* три исключения loose-механики (падающая гряда плит).
|
||||
*
|
||||
* Своего ИИ у Джафара нет — это обычный страж (seg002:085A), поэтому
|
||||
* проверять тут нечего кроме СОБЫТИЙ и ветвлений по номеру уровня. А
|
||||
* именно они и опасны: три из них — это `if (level == 13)` посреди чужого
|
||||
* кода (тряска плит, удар плитой в беге, «страж не встаёт в стойку»), и
|
||||
* сломать их можно правкой, вообще не думая про 13-й уровень.
|
||||
*
|
||||
* Порт: seg002:0544 meet_Jaffar, seg002:0517 Jaffar_exit,
|
||||
* seg006:1927 on_guard_killed, seg000:1317 check_fall_flo,
|
||||
* seg007:823/949 исключения animate_loose/loose_make_shake.
|
||||
*/
|
||||
#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"
|
||||
#include "pop_tune.h"
|
||||
#include "scene.h"
|
||||
#include "stubs.h"
|
||||
|
||||
#define E 0 /* empty */
|
||||
#define F 1 /* floor */
|
||||
#define L 11 /* loose */
|
||||
#define W 20 /* wall */
|
||||
|
||||
/* Комната с полом внизу и грядой loose в ряду 2 — она же будет играть роль
|
||||
* «комнаты сверху» для check_fall_flo. */
|
||||
static const uint8_t room_loose[30] = {
|
||||
F, F, F, F, F, F, F, F, F, F,
|
||||
W, W, W, W, W, W, W, W, W, W,
|
||||
F, F, L, L, L, L, L, L, F, F,
|
||||
};
|
||||
|
||||
/* Комната, в которую Кид входит снизу: ровный пол во всех рядах. */
|
||||
static const uint8_t room_flat[30] = {
|
||||
F, F, F, F, F, F, F, F, F, F,
|
||||
F, F, F, F, F, F, F, F, F, F,
|
||||
F, F, F, F, F, F, F, F, F, F,
|
||||
};
|
||||
|
||||
static uint8_t above_row[10];
|
||||
|
||||
static void scene_level13(uint8_t room)
|
||||
{
|
||||
pop_guard_reset();
|
||||
pop_current_level = JAFFAR_LEVEL;
|
||||
pop_leveldoor_open = 0;
|
||||
pop_guard_notice_timer = 0;
|
||||
sc_room(room_flat, room);
|
||||
sc_kid_at(1, 2, 1);
|
||||
tk_log_clear();
|
||||
}
|
||||
|
||||
/* ---- meet_Jaffar ----------------------------------------------------- */
|
||||
|
||||
TC_TEST(jaffar_notice_timer_set_leaving_room3_right)
|
||||
{
|
||||
scene_level13(JAFFAR_MEET_ROOM);
|
||||
Kid.room = JAFFAR_MEET_ROOM;
|
||||
pop_meet_jaffar();
|
||||
TC_EQ(pop_guard_notice_timer, JAFFAR_NOTICE_TIME);
|
||||
}
|
||||
|
||||
/* Дверь уровня уже открыта — значит Джафар мёртв и Кид возвращается.
|
||||
* Второй форы не даётся. */
|
||||
TC_TEST(jaffar_notice_not_given_after_victory)
|
||||
{
|
||||
scene_level13(JAFFAR_MEET_ROOM);
|
||||
Kid.room = JAFFAR_MEET_ROOM;
|
||||
pop_leveldoor_open = 2;
|
||||
pop_meet_jaffar();
|
||||
TC_EQ(pop_guard_notice_timer, 0);
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_notice_not_given_from_other_room)
|
||||
{
|
||||
scene_level13(JAFFAR_MEET_ROOM);
|
||||
Kid.room = (uint8_t)(JAFFAR_MEET_ROOM + 1);
|
||||
pop_meet_jaffar();
|
||||
TC_EQ(pop_guard_notice_timer, 0);
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_notice_is_level_gated)
|
||||
{
|
||||
scene_level13(JAFFAR_MEET_ROOM);
|
||||
pop_current_level = 12;
|
||||
Kid.room = JAFFAR_MEET_ROOM;
|
||||
pop_meet_jaffar();
|
||||
TC_EQ(pop_guard_notice_timer, 0);
|
||||
}
|
||||
|
||||
/* Счётчик убывает по кадру и останавливается на нуле. */
|
||||
TC_TEST(jaffar_notice_timer_counts_down)
|
||||
{
|
||||
uint8_t i;
|
||||
pop_united_shadow = 0;
|
||||
pop_guard_notice_timer = 3;
|
||||
for (i = 0; i < 5; i++) pop_frame_timers();
|
||||
TC_EQ(pop_guard_notice_timer, 0);
|
||||
}
|
||||
|
||||
/* ---- Джафар ЖДЁТ, пока идёт фора ------------------------------------- */
|
||||
|
||||
/* Поднять соперника с УБРАННЫМ мечом в одном ряду с Кидом и «показать» его:
|
||||
* дальше решение принимает autocontrol_guard_inactive. */
|
||||
static void jaffar_faces_kid(void)
|
||||
{
|
||||
sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6);
|
||||
Guard.sword = 0;
|
||||
can_guard_see_kid = 2;
|
||||
is_guard_notice = 0;
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_holds_still_while_notice_timer_runs)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(1);
|
||||
jaffar_faces_kid();
|
||||
pop_guard_notice_timer = JAFFAR_NOTICE_TIME;
|
||||
for (i = 0; i < 6; i++) { can_guard_see_kid = 2; pop_guard_tick(); }
|
||||
/* Стойку не сменил: меч так и в ножнах. */
|
||||
TC_EQ(Guard.sword, 0);
|
||||
}
|
||||
|
||||
/* Фора кончилась — обычное поведение стража возвращается. */
|
||||
TC_TEST(jaffar_draws_sword_after_notice_timer)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(1);
|
||||
jaffar_faces_kid();
|
||||
pop_guard_notice_timer = 0;
|
||||
for (i = 0; i < 12; i++) { can_guard_see_kid = 2; pop_guard_tick(); }
|
||||
TC_EQ(Guard.sword, SWORD_2_DRAWN);
|
||||
}
|
||||
|
||||
/* На прочих уровнях таймер ни на что не влияет: условие в оригинале
|
||||
* двойное (`level != 13 || timer == 0`), и потерять первую половину легко. */
|
||||
TC_TEST(jaffar_notice_timer_ignored_on_other_levels)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(1);
|
||||
pop_current_level = 12;
|
||||
jaffar_faces_kid();
|
||||
pop_guard_notice_timer = JAFFAR_NOTICE_TIME;
|
||||
for (i = 0; i < 12; i++) { can_guard_see_kid = 2; pop_guard_tick(); }
|
||||
TC_EQ(Guard.sword, SWORD_2_DRAWN);
|
||||
}
|
||||
|
||||
/* ---- победа и выход --------------------------------------------------- */
|
||||
|
||||
/* Смерть Джафара не открывает дверь, а ставит флаг «выход разрешён». */
|
||||
TC_TEST(jaffar_death_opens_the_exit_flag)
|
||||
{
|
||||
scene_level13(1);
|
||||
/* charid у Джафара обычный: своим charid оригинал метит только скелета
|
||||
* (enter_guard, seg002:196), а визиря отличают спрайты и события. */
|
||||
sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6);
|
||||
Guard.sword = SWORD_2_DRAWN;
|
||||
guardhp_curr = 0; /* последнее HP только что снято */
|
||||
pop_flash_time = 0;
|
||||
pop_guard_tick();
|
||||
TC_EQ(pop_leveldoor_open, 2);
|
||||
TC_EQ(pop_flash_time, JAFFAR_FLASH_TIME);
|
||||
TC_EQ(pop_flash_color, POP_FLASH_WHITE);
|
||||
}
|
||||
|
||||
/* На других уровнях смерть стража флага не ставит (иначе дверь открывалась
|
||||
* бы от любого убитого). */
|
||||
TC_TEST(jaffar_death_flag_is_level_gated)
|
||||
{
|
||||
scene_level13(1);
|
||||
pop_current_level = 12;
|
||||
sc_guard_at(CHARID_2_GUARD, 6, 2, -1, 6);
|
||||
Guard.sword = SWORD_2_DRAWN;
|
||||
guardhp_curr = 0;
|
||||
pop_guard_tick();
|
||||
TC_EQ(pop_leveldoor_open, 0);
|
||||
}
|
||||
|
||||
/* Уход ВЛЕВО при поднятом флаге жмёт кнопку комнаты 24. */
|
||||
TC_TEST(jaffar_exit_presses_button_in_room24)
|
||||
{
|
||||
scene_level13(1);
|
||||
tk_room_fg[JAFFAR_EXIT_ROOM][JAFFAR_EXIT_TILEPOS] = 15; /* opener */
|
||||
pop_leveldoor_open = 2;
|
||||
tk_log_clear();
|
||||
pop_jaffar_exit();
|
||||
TC_TRUE(tk_log_count(TK_BUTTON) >= 1);
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_exit_silent_before_victory)
|
||||
{
|
||||
scene_level13(1);
|
||||
tk_room_fg[JAFFAR_EXIT_ROOM][JAFFAR_EXIT_TILEPOS] = 15;
|
||||
pop_leveldoor_open = 0;
|
||||
tk_log_clear();
|
||||
pop_jaffar_exit();
|
||||
TC_EQ(tk_log_count(TK_BUTTON), 0);
|
||||
}
|
||||
|
||||
/* ---- три исключения loose-механики ----------------------------------- */
|
||||
|
||||
/* Сотрясение (приземление Кида) на 13-м уровне плиты НЕ трясёт: иначе оно
|
||||
* сбило бы им отложенный старт, и гряда посыпалась бы разом. */
|
||||
TC_TEST(jaffar_knock_does_not_shake_tiles)
|
||||
{
|
||||
scene_level13(1);
|
||||
sc_room(room_loose, 1);
|
||||
sc_kid_at(1, 2, 1);
|
||||
Kid.curr_row = 2;
|
||||
pop_loadkid();
|
||||
knock = -1; /* KNOCK_DOWN: ряд Кида */
|
||||
pop_check_knock();
|
||||
TC_EQ(pop_loose_modif[22], 0);
|
||||
TC_EQ(pop_loose_modif[27], 0);
|
||||
}
|
||||
|
||||
/* А на прочих уровнях трясёт — контроль, что выключен именно 13-й. */
|
||||
TC_TEST(jaffar_knock_still_shakes_on_other_levels)
|
||||
{
|
||||
scene_level13(1);
|
||||
pop_current_level = 12;
|
||||
sc_room(room_loose, 1);
|
||||
sc_kid_at(1, 2, 1);
|
||||
Kid.curr_row = 2;
|
||||
pop_loadkid();
|
||||
knock = -1;
|
||||
pop_check_knock();
|
||||
TC_EQ(pop_loose_modif[22], 0x80);
|
||||
}
|
||||
|
||||
/* Фаза с ОТРИЦАТЕЛЬНЫМ стартом на 13-м уровне не гасится, а досчитывает
|
||||
* до провала: это и есть падающая гряда. Проверяем на плите ПОЛА (тот же
|
||||
* код, что у плит-потолков, но без mob-обвязки, которая в набор не
|
||||
* линкуется). */
|
||||
TC_TEST(jaffar_negative_phase_counts_through_to_fall)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(1);
|
||||
sc_room(room_loose, 1);
|
||||
sc_kid_at(1, 0, 1);
|
||||
pop_loose_modif[22] = (uint8_t)-4; /* старт «через 4 кадра» */
|
||||
/* 4 кадра — на выход из отрицательной зоны, дальше 10 — обычный отсчёт;
|
||||
* 30 с запасом. Ванильная ветка «тряска кончилась» обнулила бы фазу на
|
||||
* первом же кадре, и плита осталась бы стоять. */
|
||||
for (i = 0; i < 30; i++) pop_loose_tick();
|
||||
TC_EQ(pop_tile_at(2, 2), 0); /* провалилась: тайл стал пустым */
|
||||
}
|
||||
|
||||
/* На прочих уровнях та же фаза читается как тряска и гаснет — плита цела. */
|
||||
TC_TEST(jaffar_negative_phase_is_shake_on_other_levels)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(1);
|
||||
pop_current_level = 12;
|
||||
sc_room(room_loose, 1);
|
||||
sc_kid_at(1, 0, 1);
|
||||
pop_loose_modif[22] = (uint8_t)-4;
|
||||
for (i = 0; i < 30; i++) pop_loose_tick();
|
||||
TC_EQ(pop_tile_at(2, 2), L);
|
||||
TC_EQ(pop_loose_modif[22], 0);
|
||||
}
|
||||
|
||||
/* ---- check_fall_flo --------------------------------------------------- */
|
||||
|
||||
/* Вход в комнату 23: шести плитам ряда 2 комнаты СВЕРХУ раздаётся
|
||||
* отрицательная фаза, остальным — ничего. */
|
||||
static void fall_flo_scene(uint8_t room)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level13(room);
|
||||
for (i = 0; i < 10; i++) above_row[i] = room_loose[20 + i];
|
||||
pop_map_set_above(above_row);
|
||||
pop_map_set_edges(0, 0, 1 /* up */, 0);
|
||||
pop_map_set_above(above_row);
|
||||
pop_loose_reset();
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_fall_flo_seeds_the_row_above)
|
||||
{
|
||||
uint8_t c, seeded = 0;
|
||||
fall_flo_scene(POP_LOOSE_TILES_ROOM1);
|
||||
pop_check_fall_flo();
|
||||
for (c = 2; c <= 7; c++)
|
||||
if ((int8_t)pop_ceil_modif[c] < 0) seeded++;
|
||||
TC_EQ(seeded, 6);
|
||||
/* Колонки вне диапазона 2..7 не тронуты (там в комнате пол, не loose). */
|
||||
TC_EQ(pop_ceil_modif[0], 0);
|
||||
TC_EQ(pop_ceil_modif[9], 0);
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_fall_flo_ignores_other_rooms)
|
||||
{
|
||||
uint8_t c;
|
||||
fall_flo_scene((uint8_t)(POP_LOOSE_TILES_ROOM1 + 1));
|
||||
pop_check_fall_flo();
|
||||
for (c = 0; c < 10; c++) TC_EQ(pop_ceil_modif[c], 0);
|
||||
}
|
||||
|
||||
TC_TEST(jaffar_fall_flo_is_level_gated)
|
||||
{
|
||||
uint8_t c;
|
||||
fall_flo_scene(POP_LOOSE_TILES_ROOM2);
|
||||
pop_current_level = 12;
|
||||
pop_check_fall_flo();
|
||||
for (c = 0; c < 10; c++) TC_EQ(pop_ceil_modif[c], 0);
|
||||
}
|
||||
|
||||
/* Повторный вход не перебивает уже идущий отсчёт (иначе гряда «зависала»
|
||||
* бы, пока игрок бегает туда-сюда). */
|
||||
TC_TEST(jaffar_fall_flo_does_not_restart_running_countdown)
|
||||
{
|
||||
fall_flo_scene(POP_LOOSE_TILES_ROOM2);
|
||||
pop_ceil_modif[3] = 5; /* плита уже падает */
|
||||
pop_check_fall_flo();
|
||||
TC_EQ(pop_ceil_modif[3], 5);
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
sc_init();
|
||||
TC_RUN(jaffar_notice_timer_set_leaving_room3_right);
|
||||
TC_RUN(jaffar_notice_not_given_after_victory);
|
||||
TC_RUN(jaffar_notice_not_given_from_other_room);
|
||||
TC_RUN(jaffar_notice_is_level_gated);
|
||||
TC_RUN(jaffar_notice_timer_counts_down);
|
||||
TC_RUN(jaffar_holds_still_while_notice_timer_runs);
|
||||
TC_RUN(jaffar_draws_sword_after_notice_timer);
|
||||
TC_RUN(jaffar_notice_timer_ignored_on_other_levels);
|
||||
TC_RUN(jaffar_death_opens_the_exit_flag);
|
||||
TC_RUN(jaffar_death_flag_is_level_gated);
|
||||
TC_RUN(jaffar_exit_presses_button_in_room24);
|
||||
TC_RUN(jaffar_exit_silent_before_victory);
|
||||
TC_RUN(jaffar_knock_does_not_shake_tiles);
|
||||
TC_RUN(jaffar_knock_still_shakes_on_other_levels);
|
||||
TC_RUN(jaffar_negative_phase_counts_through_to_fall);
|
||||
TC_RUN(jaffar_negative_phase_is_shake_on_other_levels);
|
||||
TC_RUN(jaffar_fall_flo_seeds_the_row_above);
|
||||
TC_RUN(jaffar_fall_flo_ignores_other_rooms);
|
||||
TC_RUN(jaffar_fall_flo_is_level_gated);
|
||||
TC_RUN(jaffar_fall_flo_does_not_restart_running_countdown);
|
||||
}
|
||||
@@ -211,13 +211,44 @@ TC_TEST(phys_loose_floor_breaks)
|
||||
/* Журнал заглушек: плита обязана была отвалиться (кусок отрывается —
|
||||
* pop_loose_mob_spawn) и попросить перерисовку. Это уже не
|
||||
* характеризация, а содержательная проверка — заглушка-пустышка её не
|
||||
* дала бы. Через trob loose у нас НЕ идёт (только пики/ворота), у него
|
||||
* свой pop_loose_modif — поэтому спрашиваем именно про кусок. */
|
||||
* дала бы. Пока плита ЖИВЁТ в отрисованной комнате, фазой владеет
|
||||
* pop_loose_modif, а не trob — поэтому спрашиваем именно про кусок. */
|
||||
TC_TRUE(tk_log_count(TK_MOB_SPAWN) > 0);
|
||||
TC_TRUE(tk_log_count(TK_REDRAW) > 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TC_TEST(phys_loose_survives_room_change)
|
||||
{
|
||||
/* GUARD против бага «плита не успевает провалиться»: Кид расшатал плиту
|
||||
* и ушёл в соседнюю комнату — отсчёт обязан ПЕРЕЖИТЬ смену комнаты.
|
||||
* В оригинале терять нечего (loose — обычный trob в общем списке, фаза
|
||||
* в curr_room_modif); у нас фаза живёт в pop_loose_modif[] текущей
|
||||
* комнаты, и её сдаёт/забирает пара leave_room + reset. */
|
||||
uint8_t pos = 1 * 10 + 4; /* loose-плита сцены room_loose */
|
||||
uint8_t phase = 3; /* середина отсчёта до провала (1..10) */
|
||||
|
||||
/* Саму анимацию проверяет phys_loose_floor_breaks выше; здесь нужна
|
||||
* ровно передача фазы, поэтому ставим её явно — прогон бы привязал тест
|
||||
* к тому, на каком кадре Кид добежит до плиты. */
|
||||
sc_room(room_loose, 1);
|
||||
pop_loose_modif[pos] = phase;
|
||||
|
||||
/* Уход из комнаты: фаза уезжает в room_modif + trob... */
|
||||
tk_log_clear();
|
||||
pop_loose_leave_room();
|
||||
TC_TRUE(tk_log_count(TK_TROB) > 0); /* плита сдана в общий список */
|
||||
|
||||
/* ...и возвращается при входе обратно — а не обнуляется, как раньше. */
|
||||
pop_loose_reset();
|
||||
TC_EQ(pop_loose_modif[pos], phase);
|
||||
|
||||
/* Старт уровня, наоборот, обязан фазу ЗАБЫТЬ (load_level восстанавливает
|
||||
* уровень целиком) — иначе дрожь протекала бы в следующую жизнь. */
|
||||
pop_loose_forget();
|
||||
TC_EQ(pop_loose_modif[pos], 0);
|
||||
}
|
||||
|
||||
TC_TEST(phys_running_jump_over_3tile_gap)
|
||||
{
|
||||
/* BUG-RJUMP-1. Разбег влево от колонки 8, дальше Up — разбег-прыжок.
|
||||
@@ -295,6 +326,7 @@ void main(void)
|
||||
TC_RUN(phys_jump_up_nothing_above);
|
||||
TC_RUN(phys_run_off_ledge);
|
||||
TC_RUN(phys_loose_floor_breaks);
|
||||
TC_RUN(phys_loose_survives_room_change);
|
||||
TC_RUN(phys_running_jump_over_3tile_gap);
|
||||
TC_RUN(phys_feather_fall_is_slow_and_harmless);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* t_shadow — спецсобытия УРОВНЯ 12: встреча с тенью, общий урон, слияние.
|
||||
*
|
||||
* Что здесь проверяется и почему именно тестом, а не прогоном в MAME. Вся
|
||||
* сцена уровня 12 — это набор УСЛОВИЙ, каждое из которых легко сломать,
|
||||
* ничего не трогая в самой тени: тайл меча, знак `pop_united_shadow`,
|
||||
* charid соперника, номер уровня. Проверить их в эмуляторе — значит
|
||||
* дойти до 12-го уровня и разыграть бой; здесь это шесть вызовов.
|
||||
*
|
||||
* Порт: seg002:0064 check_shadow, seg002:0536 sword_disappears,
|
||||
* seg006:2033 check_killed_shadow, seg000:1518 do_delta_hp,
|
||||
* seg003:0735 timers, seg006:1063 «появление плит».
|
||||
*
|
||||
* Сам ИИ тени (autocontrol_shadow_level12) сюда НЕ вынесен: он решает
|
||||
* «куда идти», и его исход — траектория, то есть предмет прогона со
|
||||
* сценой и эталоном, а не одиночной проверки. Набор линкует guards.c
|
||||
* (как t_mouse), поэтому добавить такой сценарий можно здесь же.
|
||||
*/
|
||||
#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"
|
||||
#include "pop_tune.h"
|
||||
#include "scene.h"
|
||||
#include "stubs.h"
|
||||
|
||||
#define E 0 /* empty */
|
||||
#define F 1 /* floor */
|
||||
#define W 20 /* wall */
|
||||
#define S 22 /* sword */
|
||||
|
||||
/* Комната 15 в объёме, который нужен событию: меч в (0,1), пол под ним. */
|
||||
static const uint8_t room15[30] = {
|
||||
F, S, F, F, F, F, F, F, E, E,
|
||||
W, W, W, W, W, W, W, W, F, F,
|
||||
W, W, W, W, W, W, W, W, W, W,
|
||||
};
|
||||
|
||||
/* Комната 2: верхний ряд с ПРОПАСТЬЮ в колонках 4..6 — те самые плиты,
|
||||
* которые появляются под ногами после слияния. */
|
||||
static const uint8_t room2[30] = {
|
||||
F, F, F, F, E, E, E, F, F, F,
|
||||
W, W, W, W, W, W, W, W, W, W,
|
||||
W, W, W, W, W, W, W, W, W, W,
|
||||
};
|
||||
|
||||
/* Общая точка входа: уровень 12, слот соперника пуст, слияния не было. */
|
||||
static void scene_level12(void)
|
||||
{
|
||||
uint8_t i;
|
||||
pop_guard_reset();
|
||||
pop_current_level = SHADOW_FIGHT_LEVEL;
|
||||
pop_united_shadow = 0;
|
||||
pop_shadow_init = 0;
|
||||
for (i = 0; i < 30; i++) tk_room_fg[SHADOW_FIGHT_ROOM][i] = room15[i];
|
||||
tk_log_clear();
|
||||
}
|
||||
|
||||
/* ---- check_shadow: когда тень поднимается ---------------------------- */
|
||||
|
||||
/* Меч ещё лежит — тени нет. Условие ОБРАТНО уровню 5 (там тень приходит,
|
||||
* пока зелье на месте), и перепутать их — самая вероятная ошибка порта. */
|
||||
TC_TEST(shadow12_waits_until_sword_taken)
|
||||
{
|
||||
scene_level12();
|
||||
TC_EQ(pop_check_shadow(SHADOW_FIGHT_ROOM), 0);
|
||||
TC_EQ(Guard.charid, 0);
|
||||
}
|
||||
|
||||
/* Меч подобран — тень падает в комнату (init_shad_12 + seq_7_fall). */
|
||||
TC_TEST(shadow12_rises_when_sword_gone)
|
||||
{
|
||||
scene_level12();
|
||||
tk_room_fg[SHADOW_FIGHT_ROOM][SHADOW_SWORD_TILEPOS] = F;
|
||||
TC_EQ(pop_check_shadow(SHADOW_FIGHT_ROOM), 1);
|
||||
TC_EQ(Guard.charid, CHARID_1_SHADOW);
|
||||
TC_EQ(Guard.x, 0x51);
|
||||
/* y = 232 — НИЖЕ поля (192), и это не ошибка данных: поле беззнаковое,
|
||||
* тень «падает» из-под комнаты и появляется сверху. А curr_row при
|
||||
* этом РОВНО ИЗ ТАБЛИЦЫ (0), а не пересчитанный из y (тот дал бы −1):
|
||||
* do_init_shad копирует семь полей как есть и ряд не трогает. Обе
|
||||
* величины закреплены тестом — «починить» их выглядит соблазнительно. */
|
||||
TC_EQ(Guard.y, 0xE8);
|
||||
TC_EQ(Guard.curr_row, 0);
|
||||
TC_EQ(Guard.direction, 0); /* dir_0_right */
|
||||
TC_EQ(Guard.room, SHADOW_FIGHT_ROOM);
|
||||
TC_EQ(guardhp_curr, 4);
|
||||
}
|
||||
|
||||
/* После слияния тень не поднимается больше НИКОГДА (флаг ушёл в −1). */
|
||||
TC_TEST(shadow12_never_rises_after_uniting)
|
||||
{
|
||||
scene_level12();
|
||||
tk_room_fg[SHADOW_FIGHT_ROOM][SHADOW_SWORD_TILEPOS] = F;
|
||||
pop_united_shadow = -1;
|
||||
TC_EQ(pop_check_shadow(SHADOW_FIGHT_ROOM), 0);
|
||||
TC_EQ(Guard.charid, 0);
|
||||
}
|
||||
|
||||
/* В чужой комнате событие не срабатывает — там поднимается обычный страж
|
||||
* (возврат 0 = «занимайся слотом сам»). */
|
||||
TC_TEST(shadow12_other_room_leaves_slot_alone)
|
||||
{
|
||||
scene_level12();
|
||||
TC_EQ(pop_check_shadow((uint8_t)(SHADOW_FIGHT_ROOM - 1)), 0);
|
||||
TC_EQ(Guard.charid, 0);
|
||||
}
|
||||
|
||||
/* ---- sword_disappears ------------------------------------------------ */
|
||||
|
||||
TC_TEST(shadow12_sword_vanishes_leaving_room18)
|
||||
{
|
||||
scene_level12();
|
||||
Kid.room = SWORD_GONE_ROOM;
|
||||
tk_modif[SHADOW_SWORD_TILEPOS] = 5; /* блеск меча */
|
||||
pop_sword_disappears();
|
||||
TC_EQ(tk_room_fg[SHADOW_FIGHT_ROOM][SHADOW_SWORD_TILEPOS], F);
|
||||
/* Модификатор гасится вместе с тайлом: ненулевой modif рисует на полу
|
||||
* «фальшивый» узор (та же причина, что у поднятого зелья). */
|
||||
TC_EQ(tk_modif[SHADOW_SWORD_TILEPOS], 0);
|
||||
}
|
||||
|
||||
TC_TEST(shadow12_sword_stays_leaving_other_room)
|
||||
{
|
||||
scene_level12();
|
||||
Kid.room = (uint8_t)(SWORD_GONE_ROOM - 1);
|
||||
pop_sword_disappears();
|
||||
TC_EQ(tk_room_fg[SHADOW_FIGHT_ROOM][SHADOW_SWORD_TILEPOS], S);
|
||||
}
|
||||
|
||||
/* На других уровнях комната 18 — обычная комната. */
|
||||
TC_TEST(shadow12_sword_event_is_level_gated)
|
||||
{
|
||||
scene_level12();
|
||||
pop_current_level = 11;
|
||||
Kid.room = SWORD_GONE_ROOM;
|
||||
pop_sword_disappears();
|
||||
TC_EQ(tk_room_fg[SHADOW_FIGHT_ROOM][SHADOW_SWORD_TILEPOS], S);
|
||||
}
|
||||
|
||||
/* ---- таймер вспышки -------------------------------------------------- */
|
||||
|
||||
/* Счётчик досчитывает до нуля и уходит в −1 — «уже слились». Ноль как
|
||||
* конечное состояние был бы неотличим от «слияния не было». */
|
||||
TC_TEST(shadow12_timer_ends_at_minus_one)
|
||||
{
|
||||
pop_united_shadow = 3;
|
||||
pop_frame_timers(); TC_EQ(pop_united_shadow, 2);
|
||||
pop_frame_timers(); TC_EQ(pop_united_shadow, 1);
|
||||
pop_frame_timers(); TC_EQ(pop_united_shadow, -1);
|
||||
pop_frame_timers(); TC_EQ(pop_united_shadow, -1); /* и больше не меняется */
|
||||
}
|
||||
|
||||
TC_TEST(shadow12_timer_idle_when_zero)
|
||||
{
|
||||
pop_united_shadow = 0;
|
||||
pop_frame_timers();
|
||||
TC_EQ(pop_united_shadow, 0);
|
||||
}
|
||||
|
||||
/* ---- общий урон (do_delta_hp) ---------------------------------------- */
|
||||
|
||||
static void hp_scene(uint8_t charid, uint8_t level)
|
||||
{
|
||||
scene_level12();
|
||||
pop_current_level = level;
|
||||
Guard.charid = charid;
|
||||
guardhp_curr = guardhp_max = 4;
|
||||
hitp_beg_lev = 3;
|
||||
pop_kid_hp_reset();
|
||||
guardhp_delta = -1;
|
||||
hitp_delta = 0;
|
||||
}
|
||||
|
||||
/* Ранил тень — ранил себя: урон КОПИРУЕТСЯ, а не делится. */
|
||||
TC_TEST(shadow12_damage_is_shared)
|
||||
{
|
||||
hp_scene(CHARID_1_SHADOW, SHADOW_FIGHT_LEVEL);
|
||||
pop_do_delta_hp();
|
||||
TC_EQ(guardhp_curr, 3);
|
||||
TC_EQ(hitp_curr, 2);
|
||||
}
|
||||
|
||||
TC_TEST(shadow12_damage_not_shared_with_guard)
|
||||
{
|
||||
hp_scene(CHARID_2_GUARD, SHADOW_FIGHT_LEVEL);
|
||||
pop_do_delta_hp();
|
||||
TC_EQ(guardhp_curr, 3);
|
||||
TC_EQ(hitp_curr, 3);
|
||||
}
|
||||
|
||||
/* Тень уровней 4/5/6 боевой не бывает, но проверка обязана быть по УРОВНЮ,
|
||||
* а не только по charid: иначе безобидная тень уровня 5 делилась бы HP. */
|
||||
TC_TEST(shadow12_damage_not_shared_on_other_level)
|
||||
{
|
||||
hp_scene(CHARID_1_SHADOW, 5);
|
||||
pop_do_delta_hp();
|
||||
TC_EQ(guardhp_curr, 3);
|
||||
TC_EQ(hitp_curr, 3);
|
||||
}
|
||||
|
||||
/* ---- check_killed_shadow --------------------------------------------- */
|
||||
|
||||
static void kill_scene(uint8_t charid, int8_t kid_alive, int8_t guard_alive)
|
||||
{
|
||||
scene_level12();
|
||||
Guard.charid = charid;
|
||||
Guard.alive = guard_alive;
|
||||
guardhp_curr = guardhp_max = 4;
|
||||
hitp_beg_lev = 3;
|
||||
pop_kid_hp_reset();
|
||||
Kid.charid = CHARID_0_KID;
|
||||
Kid.alive = kid_alive;
|
||||
guardhp_delta = hitp_delta = 0;
|
||||
pop_flash_time = 0;
|
||||
pop_immortal = 0;
|
||||
}
|
||||
|
||||
/* Тень мертва — Киду отсчитывается 100 урона, то есть гарантированно всё. */
|
||||
TC_TEST(shadow12_killing_shadow_kills_kid)
|
||||
{
|
||||
kill_scene(CHARID_1_SHADOW, -1, 0);
|
||||
pop_check_killed_shadow();
|
||||
TC_EQ(hitp_delta, -3);
|
||||
TC_TRUE(pop_flash_time != 0);
|
||||
TC_EQ(pop_flash_color, POP_FLASH_WHITE);
|
||||
}
|
||||
|
||||
/* И симметрично: смерть Кида добивает тень. */
|
||||
TC_TEST(shadow12_killing_kid_kills_shadow)
|
||||
{
|
||||
kill_scene(CHARID_1_SHADOW, 0, -1);
|
||||
pop_check_killed_shadow();
|
||||
TC_EQ(guardhp_delta, -4);
|
||||
}
|
||||
|
||||
/* Со стражем связи нет — условие оригинала `(charid | charid) == shadow`
|
||||
* проходит только для пары Кид/тень. */
|
||||
TC_TEST(shadow12_kill_link_ignores_guard)
|
||||
{
|
||||
kill_scene(CHARID_2_GUARD, -1, 0);
|
||||
pop_check_killed_shadow();
|
||||
TC_EQ(hitp_delta, 0);
|
||||
TC_EQ(pop_flash_time, 0);
|
||||
}
|
||||
|
||||
/* Оба живы — событие молчит (иначе бой убивал бы обоих в первом же кадре). */
|
||||
TC_TEST(shadow12_kill_link_silent_while_both_alive)
|
||||
{
|
||||
kill_scene(CHARID_1_SHADOW, -1, -1);
|
||||
pop_check_killed_shadow();
|
||||
TC_EQ(hitp_delta, 0);
|
||||
TC_EQ(guardhp_delta, 0);
|
||||
}
|
||||
|
||||
/* ---- «появление плит» ------------------------------------------------ */
|
||||
|
||||
/* Сцена: Кид стоит в комнате 2 на колонке 4 верхнего ряда — прямо над
|
||||
* пропастью. До слияния он обязан провалиться, после — достроить пол. */
|
||||
static void floors_scene(void)
|
||||
{
|
||||
uint8_t i;
|
||||
scene_level12();
|
||||
for (i = 0; i < 30; i++) tk_room_fg[2][i] = room2[i];
|
||||
sc_room(room2, 2);
|
||||
sc_kid_at(4, 0, 1);
|
||||
sc_trace_clear();
|
||||
}
|
||||
|
||||
TC_TEST(shadow12_floors_appear_after_uniting)
|
||||
{
|
||||
floors_scene();
|
||||
pop_united_shadow = -1;
|
||||
sc_run(0, 2);
|
||||
/* Не упал: действие осталось стоечным, ряд тот же. */
|
||||
TC_EQ(Kid.curr_row, 0);
|
||||
TC_TRUE(Kid.action != 4); /* actions_4_in_freefall */
|
||||
/* Плита появилась И в карте комнаты, И в данных уровня. */
|
||||
TC_EQ(pop_tile_at(4, 0), 1);
|
||||
TC_EQ(tk_room_fg[2][4], 1);
|
||||
/* Перерисованы две клетки — сама и соседняя справа, на обеих страницах. */
|
||||
TC_TRUE(tk_log_count(TK_REDRAW) >= 2);
|
||||
}
|
||||
|
||||
/* До слияния пропасть остаётся пропастью. */
|
||||
TC_TEST(shadow12_floors_hidden_before_uniting)
|
||||
{
|
||||
floors_scene();
|
||||
pop_united_shadow = 0;
|
||||
sc_run(0, 2);
|
||||
TC_EQ(pop_tile_at(4, 0), 0);
|
||||
TC_TRUE(Kid.curr_row != 0 || Kid.action == 4);
|
||||
}
|
||||
|
||||
/* Событие привязано к КОМНАТЕ: в комнате 3 того же уровня пола нет. */
|
||||
TC_TEST(shadow12_floors_only_in_their_rooms)
|
||||
{
|
||||
floors_scene();
|
||||
pop_united_shadow = -1;
|
||||
Kid.room = 3;
|
||||
sc_run(0, 2);
|
||||
TC_EQ(pop_tile_at(4, 0), 0);
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
sc_init();
|
||||
TC_RUN(shadow12_waits_until_sword_taken);
|
||||
TC_RUN(shadow12_rises_when_sword_gone);
|
||||
TC_RUN(shadow12_never_rises_after_uniting);
|
||||
TC_RUN(shadow12_other_room_leaves_slot_alone);
|
||||
TC_RUN(shadow12_sword_vanishes_leaving_room18);
|
||||
TC_RUN(shadow12_sword_stays_leaving_other_room);
|
||||
TC_RUN(shadow12_sword_event_is_level_gated);
|
||||
TC_RUN(shadow12_timer_ends_at_minus_one);
|
||||
TC_RUN(shadow12_timer_idle_when_zero);
|
||||
TC_RUN(shadow12_damage_is_shared);
|
||||
TC_RUN(shadow12_damage_not_shared_with_guard);
|
||||
TC_RUN(shadow12_damage_not_shared_on_other_level);
|
||||
TC_RUN(shadow12_killing_shadow_kills_kid);
|
||||
TC_RUN(shadow12_killing_kid_kills_shadow);
|
||||
TC_RUN(shadow12_kill_link_ignores_guard);
|
||||
TC_RUN(shadow12_kill_link_silent_while_both_alive);
|
||||
TC_RUN(shadow12_floors_appear_after_uniting);
|
||||
TC_RUN(shadow12_floors_hidden_before_uniting);
|
||||
TC_RUN(shadow12_floors_only_in_their_rooms);
|
||||
}
|
||||
Reference in New Issue
Block a user