SprPoP: мёртвое тело больше не приземляется в присед, надпись смерти не залипает
Два независимых фикса, оба проверены в MAME. 1. ПРИЗЕМЛЕНИЕ МЁРТВОГО (симптом «убили Кида, а он сел этажом ниже»). Оригинал спрашивает у приземляющегося, жив ли он (seg005:0173): вся развязка «мягко / средне / разбиться» отведена ЖИВОМУ, телу — своя ветка (добить HP, звук падения насмерть, seq_22). У нас развилки не было, и в глаза это не бросалось только из-за высоты: тело, сброшенное ударом с ОДНОГО ряда, набирает fall_y < 22 — урона нет, «последнее HP» не тратится, ветка «разбился» не выбирается никогда. Труп уходил в мягкое приземление и садился (кадр 109). Цена — один тест байта на вызов land(), то есть на событие касания земли, а не на кадр. Живой путь не изменился ни на операцию. 2. ЗАЛИПАВШАЯ НАДПИСЬ «Press Button to Continue». Счётчик кадров смерти живёт снаружи главного витка и потому переживает возврат на заставку. Ответ игрока кнопкой его обнулял, а выход по таймауту (24 с молчания -> title) уходил мимо сброса. Дальше счётчик оставался израсходованным на всю сессию, и в следующей игре ПЕРВАЯ же смерть мгновенно уводила в title, не показав надписи; лечилось только перезапуском программы. Сброс поставлен на входе в игровой маршрут — закрывает и остальные боковые дороги (выпадение за нижнюю границу, смена уровня). Здесь же довезена связка находок 12/13 аудита: смерть безоружного у обрыва уходит в свою последовательность (seq_81), а прижатие к полу осталось страховкой для прочих веток — снять его целиком не вышло дважды, подробности в комментарии guards.c. Тесты: t_death дополнен обеими сторонами развилки (мёртвый обязан разбиться, живой с той же высоты — сесть без урона), 18 проверок; все 16 наборов host-тестов зелёные. В доску записан CLIMB-VS-GUARD: Кид подтягивается к стражу этажом выше — у нас удар порой смертелен, в оригинале Кид срывается без урона. Цепочка засчитывания удара сверена с оригиналом и совпадает дословно, расходятся входные данные. Лучшая зацепка — ЗВУК: оригинал играет взмах клинка (11) при любом уколе, до всякой проверки попадания, а у нас слышен упор в стену (8) — значит страж не атакует, а сталкивается. Набор звуков проверен и не виноват. Отложено по решению пользователя. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
This commit is contained in:
@@ -113,6 +113,101 @@ TC_TEST(death_at_ledge_keeps_body_in_place)
|
||||
TC_EQ(Kid.fall_y, 0);
|
||||
}
|
||||
|
||||
/* --- ДВЕ СМЕРТИ БЕЗОРУЖНОГО: на месте и сброшенным с уступа ---------- *
|
||||
*
|
||||
* Оригинал выбирает между ними по обстановке ПОЗАДИ: есть опора или до
|
||||
* кромки меньше четырёх — «заколот на месте»; сзади обрыв — отдельная
|
||||
* последовательность падения замертво (docs/sdlpop_audit.md, находка 12).
|
||||
*
|
||||
* Проверяем сам ВЫБОР: последовательности должны различаться. Сравнение
|
||||
* с конкретным номером было бы хрупким — смещения приходят из таблицы,
|
||||
* извлечённой из данных оригинала. */
|
||||
|
||||
static uint16_t seq_after_unarmed_death(const uint8_t *room, uint8_t col, int8_t dir)
|
||||
{
|
||||
sc_room(room, 1);
|
||||
sc_kid_at(col, 2, dir);
|
||||
hitp_curr = 3;
|
||||
Kid.sword = 0; /* безоружен: падение уже спрятало меч */
|
||||
Kid.alive = -1;
|
||||
pop_kid_dead = 0;
|
||||
Kid.action = 99;
|
||||
pop_check_sword_hurt();
|
||||
pop_do_delta_hp();
|
||||
return Kid.curr_seq;
|
||||
}
|
||||
|
||||
TC_TEST(unarmed_death_with_floor_behind_differs_from_ledge)
|
||||
{
|
||||
uint16_t on_floor, at_ledge;
|
||||
/* Лицом ВПРАВО в середине сплошного пола: сзади (слева) опора. */
|
||||
on_floor = seq_after_unarmed_death(room_flat, 4, 1);
|
||||
/* Лицом ВЛЕВО у кромки: сзади (справа) обрыв — колонки 5..9 пусты. */
|
||||
at_ledge = seq_after_unarmed_death(room_ledge, 4, -1);
|
||||
TC_TRUE(on_floor != at_ledge);
|
||||
}
|
||||
|
||||
TC_TEST(unarmed_death_at_ledge_is_lethal_too)
|
||||
{
|
||||
(void)seq_after_unarmed_death(room_ledge, 4, -1);
|
||||
TC_EQ(hitp_curr, 0);
|
||||
}
|
||||
|
||||
/* --- ПРИЗЕМЛЕНИЕ МЁРТВОГО ТЕЛА ---------------------------------------
|
||||
*
|
||||
* Оригинал спрашивает у приземляющегося, жив ли он (seg005:0173): вся
|
||||
* развязка «мягко / средне / разбиться» отведена ЖИВОМУ, а телу — своя
|
||||
* ветка (добить HP, звук падения насмерть, seq_22).
|
||||
*
|
||||
* У нас развилки не было, и это не бросалось в глаза только из-за высоты:
|
||||
* тело, сброшенное ударом с ОДНОГО ряда, набирает fall_y < 22 — урона нет,
|
||||
* «последнее HP» не тратится, ветка «разбился» не выбирается никогда, и
|
||||
* труп приземлялся в ПРИСЕД (кадр 109). Отсюда симптом «убитого Кида
|
||||
* уронили, а он сел этажом ниже».
|
||||
*
|
||||
* Пара тестов держит обе стороны развилки: мёртвый обязан разбиться,
|
||||
* живой на той же высоте — сесть, как и раньше. */
|
||||
|
||||
#define FRAME_109_CROUCH 109
|
||||
|
||||
/* Уронить персонажа с ряда 1 на пол ряда 2 и дать физике доиграть. */
|
||||
static void drop_from_row1(const uint8_t *room, uint8_t col, uint8_t alive, uint8_t hp)
|
||||
{
|
||||
uint8_t i;
|
||||
sc_room(room, 1);
|
||||
sc_kid_at(col, 1, 1 /* лицом вправо */);
|
||||
hitp_curr = hp;
|
||||
Kid.alive = (int8_t)alive; /* -1 жив, 0 мёртв (соглашение оригинала) */
|
||||
Kid.sword = 0;
|
||||
pop_kid_dead = 0;
|
||||
Kid.action = 4; /* свободное падение */
|
||||
for (i = 0; i < 40; i++) pop_phys_tick();
|
||||
}
|
||||
|
||||
TC_TEST(dead_body_falls_crushed_not_crouched)
|
||||
{
|
||||
/* Мёртвое тело падает на ОДИН ряд: высоты не хватает ни на урон, ни на
|
||||
* «последнее HP», поэтому до фикса оно уходило в мягкое приземление. */
|
||||
drop_from_row1(room_flat, 4, 0 /* мёртв */, 0);
|
||||
TC_EQ(Kid.curr_row, 2); /* долетело до пола */
|
||||
TC_EQ(Kid.fall_y, 0); /* скорость погашена */
|
||||
TC_FALSE(Kid.frame == FRAME_109_CROUCH); /* НЕ присед */
|
||||
TC_EQ(pop_kid_dead, 1); /* пошли дорогой «разбился» */
|
||||
}
|
||||
|
||||
TC_TEST(living_soft_land_from_row1_still_crouches)
|
||||
{
|
||||
/* Обратная сторона развилки: живой с той же высоты обязан сесть, как и
|
||||
* до фикса. Если этот тест покраснеет — развилка съела живую ветку. */
|
||||
drop_from_row1(room_flat, 4, (uint8_t)-1 /* жив */, 3);
|
||||
TC_EQ(Kid.curr_row, 2);
|
||||
/* Кадр не проверяем: присед — это ПОСЛЕДОВАТЕЛЬНОСТЬ, и к сороковому
|
||||
* тику она уже ушла с начального 109 (первый прогон теста поймал это
|
||||
* на кадре 107). Держим то, что действительно различает ветки. */
|
||||
TC_EQ(pop_kid_dead, 0); /* живого не разбило */
|
||||
TC_EQ(hitp_curr, 3); /* один ряд — без урона */
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
sc_init();
|
||||
@@ -122,5 +217,9 @@ int main(void)
|
||||
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);
|
||||
TC_RUN(unarmed_death_with_floor_behind_differs_from_ledge);
|
||||
TC_RUN(unarmed_death_at_ledge_is_lethal_too);
|
||||
TC_RUN(dead_body_falls_crushed_not_crouched);
|
||||
TC_RUN(living_soft_land_from_row1_still_crouches);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user