SprPoP: тесты смерти от меча (базовое поведение до правок 12/13)

КОД ИГРЫ НЕ МЕНЯЛСЯ.  Новый набор t_death — 9 проверок, всего наборов 16.

Заведён ПЕРЕД правкой находок 12/13, чтобы поймать деградацию: правка 13 в
изоляции уже ломала смерть (мёртвый оставался с ненулевой скоростью
падения, проваливался за нижнюю границу, игра уходила на рестарт, не
показав тела).  Поэтому проверяется ровно то, на что эти правки влияют:

* удар не в боевой стойке смертелен независимо от запаса HP;
* удар с мечом снимает одно HP, на последнем — убивает;
* ПЕРЕЖИВШИЙ удар ставится на пол своего ряда с нулевой скоростью падения
  (в оригинале это единственная ветка, где координата трогается);
* тело после смерти остаётся в своём ряду — и на ровном полу, и у самого
  обрыва (целевая сцена находки 12; после правки ожидание изменится
  осознанно).

По дороге тест дважды показал не баг движка, а мои ошибки в самой сцене:
обвязка выставляет признак «жив» только стражу, а урон применяется не
сразу — удар выставляет дельту, и HP меняет отдельный шаг кадра, как в
оригинале.  Оба раза чинился тест.

VANILLA/BUGFIXED: записано, что переключатель уже существует в настройках
и зафиксирован в положении VANILLA, второй заводить не нужно.  Отмечено
главное следствие — наш «ванильный» билд УЖЕ не чистая ваниль (часть
ванильных багов пофикшена), поэтому при разделении режимов придётся
пройтись по сделанным отступлениям и распределить их; отдельные фиксы
(падение сквозь стену) могут быть сделаны и в нынешнем билде.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
2026-08-31 20:23:35 +03:00
parent 90e304f071
commit a3aaa30e53
3 changed files with 149 additions and 4 deletions
@@ -67,12 +67,28 @@
То есть когда появится BUGFIXED, этому тесту понадобится ожидание,
зависящее от режима, — и он готов стать первым таким.
## Переключатель уже есть — второго не нужно
Уточнено 2026-08-31: в настройках игры ПЕРЕКЛЮЧАТЕЛЬ VANILLA/ENHANCED
СУЩЕСТВУЕТ (`docs/menu_settings_plan.md`), просто сейчас он жёстко
зафиксирован в положении VANILLA. Отдельную сущность заводить не надо —
эта задача про то, чтобы наполнить смыслом уже имеющееся положение
ENHANCED.
**Наш «ванильный» билд уже не чистая ваниль.** Часть ванильных багов у
нас пофикшена по ходу портирования. Значит:
* нельзя считать текущее поведение эталоном ванили — оно смешанное;
* при разделении режимов придётся пройтись по уже сделанным фиксам и
решить по каждому, остаётся он в VANILLA или уезжает в ENHANCED;
* и наоборот: отдельные исправления (например, падение сквозь стену)
вполне могут быть сделаны прямо в нынешнем «ванильном» билде, если
сочтём, что играбельность важнее буквальности.
## Что предстоит решить
1. **Как переключать.** Ключ сборки (два разных `.exe`) или настройка в
`POP.CFG` с проверкой в рантайме. У нас уже есть VANILLA/ENHANCED в
меню настроек (`docs/menu_settings_plan.md`) — возможно, это то же
самое измерение и стоит объединить, а не заводить второе.
1. **Что считать ванилью на практике.** Составить список уже сделанных
отступлений от оригинала и распределить их между режимами.
2. **Цена рантайм-проверки.** Условия попадают в физику и `play_seq`, то
есть в горячий путь. Если переключать в рантайме, проверка должна
быть дешевле самого фикса: флаг в резиденте, а не вызов через банк.
+3
View File
@@ -65,6 +65,9 @@ OBJS_mouse := $(OBJS_phys) build/eng_guards.rel
OBJS_shadow := $(OBJS_phys) build/eng_guards.rel
# t_jaffar — спецсобытия уровня 13 (встреча, победа, выход, падающая гряда).
OBJS_jaffar := $(OBJS_phys) build/eng_guards.rel
# t_death — смерть от меча: HP, признак смерти и, главное, координата с
# остаточной скоростью падения (находки 12/13 в docs/sdlpop_audit.md).
OBJS_death := $(OBJS_phys) build/eng_guards.rel
# t_cfg — весь codec POP.CFG из единственного прикладного модуля.
OBJS_cfg := build/eng_pop_config.rel build/bank_stub.rel
# pop_app — чистая таблица переходов, без DSS/графики.
+126
View File
@@ -0,0 +1,126 @@
/*
* t_death — смерть от меча: что происходит с координатой и состоянием.
*
* Набор заведён ПЕРЕД правкой находок 12/13 (docs/sdlpop_audit.md), чтобы
* зафиксировать нынешнее поведение и поймать деградацию. История вопроса:
* правка 13 в изоляции уже ломала смерть — мёртвый Кид оставался с
* ненулевой скоростью падения, проваливался за нижнюю границу и игра
* уходила на рестарт, не показав тела.
*
* Проверяем ровно то, на что эти правки влияют:
* - HP и признак смерти;
* - КООРДИНАТУ по Y и остаточную скорость падения (в этом вся суть);
* - что смерть наступает при ударе не в боевой стойке (порт оригинала:
* «ранение вне боевой стойки означает смерть»).
*/
#include "tcheck.h"
#include "scene.h"
#include "stubs.h"
#include "pop_kid.h"
#include "pop_map.h"
#include "pop_guard.h"
#define E 0
#define F 1
#define W 20
/* Ровный пол во всю ширину: ряд 2 — пол, выше пусто. Комната без краёв,
* чтобы смерть не смешивалась с падением. */
static const uint8_t room_flat[30] = {
E, E, E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E, E, E,
F, F, F, F, F, F, F, F, F, F,
};
/* Площадка, обрывающаяся справа: колонки 0-4 — пол, 5-9 пусто. Кид у
* самого края спиной к обрыву — целевая сцена находки 12 (в оригинале
* такой удар отправляет в отдельную последовательность падения замертво). */
static const uint8_t room_ledge[30] = {
E, E, E, E, E, E, E, E, E, E,
E, E, E, E, E, E, E, E, E, E,
F, F, F, F, F, E, E, E, E, E,
};
/* Поставить сцену «страж бьёт Кида» и нанести удар. */
static void strike_kid(const uint8_t *room, uint8_t col, uint8_t hp, uint8_t sword)
{
sc_room(room, 1);
sc_kid_at(col, 2, 1 /* лицом вправо */);
hitp_curr = hp;
Kid.sword = sword;
pop_kid_dead = 0;
/* Признак «жив» обвязка ставит только стражу, поэтому Киду задаём его
* сами: обработчик удара первым делом отсекает уже мёртвого. */
Kid.alive = -1;
/* Удар оформляется действием 99 «ранен» — так его помечает боевая
* проверка оригинала, а разбирает pop_check_sword_hurt. */
Kid.action = 99;
pop_check_sword_hurt();
/* Удар выставляет ДЕЛЬТУ, а HP меняет отдельный шаг кадра — как в
* оригинале. Без него hitp_curr остался бы прежним, и тест мерил бы
* не то. */
pop_do_delta_hp();
}
TC_TEST(death_unarmed_hit_is_lethal)
{
strike_kid(room_flat, 4, 3 /* HP */, 0 /* без меча */);
/* Удар не в боевой стойке смертелен независимо от запаса HP. */
TC_EQ(hitp_curr, 0);
}
TC_TEST(death_armed_hit_costs_one_hp)
{
strike_kid(room_flat, 4, 3, 2 /* меч вынут */);
TC_EQ(hitp_curr, 2);
}
TC_TEST(death_armed_last_hp_kills)
{
strike_kid(room_flat, 4, 1, 2);
TC_EQ(hitp_curr, 0);
}
/* --- КООРДИНАТА ПОСЛЕ УДАРА: суть находок 12 и 13 -------------------- */
TC_TEST(survivor_is_placed_on_floor)
{
/* Пережил удар — оригинал ставит его на пол своего ряда и гасит
* скорость падения. Это единственная ветка, где он так делает. */
strike_kid(room_flat, 4, 3, 2);
TC_EQ(Kid.fall_y, 0);
TC_EQ(Kid.y, pop_y_land[Kid.curr_row + 1]);
}
TC_TEST(death_on_flat_floor_keeps_body_in_place)
{
/* Смерть на ровном полу: тело обязано остаться в своём ряду. Если
* координата или скорость падения уедут, физика утащит труп вниз —
* ровно так ломалась игра при правке 13 в одиночку. */
strike_kid(room_flat, 4, 1, 2);
TC_EQ(Kid.curr_row, 2);
TC_EQ(Kid.fall_y, 0);
}
TC_TEST(death_at_ledge_keeps_body_in_place)
{
/* Смерть у самого обрыва — целевая сцена находки 12. Сейчас мы
* ставим «заколот на месте» и удерживаем тело; в оригинале здесь
* своя последовательность падения замертво. Тест фиксирует НЫНЕШНЕЕ
* поведение: после правки 12+13 ожидание изменится осознанно. */
strike_kid(room_ledge, 4, 1, 2);
TC_EQ(Kid.curr_row, 2);
TC_EQ(Kid.fall_y, 0);
}
int main(void)
{
sc_init();
TC_RUN(death_unarmed_hit_is_lethal);
TC_RUN(death_armed_hit_costs_one_hp);
TC_RUN(death_armed_last_hp_kills);
TC_RUN(survivor_is_placed_on_floor);
TC_RUN(death_on_flat_floor_keeps_body_in_place);
TC_RUN(death_at_ledge_keeps_body_in_place);
return 0;
}