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:
@@ -999,6 +999,7 @@ uint8_t pop_demo_kid_ai(void) __banked
|
||||
#define FRAME_154_POKING 154
|
||||
#define SEQ_69_ATTACK_WAS_PARRIED 69
|
||||
#define SEQ_74_HIT_BY_SWORD 74
|
||||
#define SEQ_81_PUSHED_OFF_LEDGE 81 /* заколот у обрыва — падает замертво */
|
||||
#define SEQ_85_STABBED_TO_DEATH 85
|
||||
|
||||
/* refractimer (seg002:36) — «отдышка» стража после того, как его ранили. */
|
||||
@@ -1008,10 +1009,40 @@ static const uint8_t REFRACTIMER[NUM_GUARD_SKILLS] =
|
||||
/* hurt_by_sword (seg002): применить попадание к АКТИВНОМУ персонажу.
|
||||
* Без меча в руке любое попадание смертельно; с мечом — минус 1 HP и кадр
|
||||
* «получил удар».
|
||||
* УПРОЩЕНИЕ: ветку «сбит с уступа» (seq_81, когда сзади пусто и до кромки
|
||||
* меньше 4) не портируем — ей нужны тайловые запросы ОТ Char, а pop_map
|
||||
* пока умеет только от Kid. На ровном полу (тайл сзади не пустой) оригинал
|
||||
* идёт ровно нашей веткой. */
|
||||
*
|
||||
* СМЕРТЬ БЕЗОРУЖНОГО БЫВАЕТ ДВУХ ВИДОВ, и выбор между ними делается по
|
||||
* обстановке ПОЗАДИ (seg002): если сзади есть опора или до кромки меньше
|
||||
* четырёх — «заколот на месте» (seq_85); если сзади пусто и от кромки
|
||||
* далеко — «сброшен с уступа» (seq_81), отдельная последовательность,
|
||||
* которая сама отыгрывает падение замертво.
|
||||
*
|
||||
* Вторая ветка появилась 2026-08-31 (docs/sdlpop_audit.md, находка 12):
|
||||
* раньше её не было, потому что считалось, будто тайловые запросы от Char
|
||||
* недоступны — на деле они давно работают, не хватало объявлений.
|
||||
*
|
||||
* Замечание к сцене: падение первым делом убирает меч (start_fall), так
|
||||
* что персонаж, сбитый в пропасть во время боя, к следующему удару уже
|
||||
* безоружен — и попадает сюда же. */
|
||||
/* ПРИЖАТЬ К ПОЛУ СВОЕГО РЯДА — только для ПЕРЕЖИВШЕГО удар.
|
||||
*
|
||||
* В оригинале (seg002, ветка seq_74_hit_by_sword) эти две строки стоят
|
||||
* ВНУТРИ ветки выжившего; смертельные ветки координату не трогают.
|
||||
*
|
||||
* У нас они долго выполнялись безусловно и работали СТРАХОВКОЙ за
|
||||
* отсутствующую ветку «сброшен с уступа»: убитого в воздухе прижимали к
|
||||
* полу, иначе он продолжал падать и выпадал за нижнюю границу, а игра
|
||||
* уходила на рестарт, не показав тела (проверено на живой машине
|
||||
* 2026-08-31 — правка в одиночку ломала смерть).
|
||||
*
|
||||
* Снято ПОСЛЕ появления ветки seq_81: теперь смерть у обрыва отыгрывает
|
||||
* своя последовательность, и страховка больше не нужна. Порядок именно
|
||||
* такой и важен — сперва ветка, потом снятие. */
|
||||
static void hurt_stand_on_floor(void)
|
||||
{
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
Char.fall_y = 0;
|
||||
}
|
||||
|
||||
static void hurt_by_sword(void)
|
||||
{
|
||||
if (Char.alive >= 0) return;
|
||||
@@ -1032,19 +1063,22 @@ static void hurt_by_sword(void)
|
||||
* Костыля «снять бессмертие на время вызова» здесь БОЛЬШЕ НЕТ:
|
||||
* pop_take_hp гасит только урон меньше 100, а тут ровно 100. */
|
||||
pop_take_hp(100);
|
||||
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH);
|
||||
if (pop_tile_behind_char() != 0 || pop_dist_to_edge_weight() < 4)
|
||||
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH); /* есть опора сзади */
|
||||
else
|
||||
pop_char_set_seq(SEQ_81_PUSHED_OFF_LEDGE); /* сзади обрыв */
|
||||
} else if (Char.charid == CHARID_0_KID && pop_immortal) {
|
||||
/* ЧИТ, уровень 1 и выше: в боевой стойке удары не отнимают HP.
|
||||
* Кадр «получил удар» оставляем — иначе бой перестаёт читаться,
|
||||
* да и оригинал на выживший удар ставит ровно его. */
|
||||
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||
hurt_stand_on_floor();
|
||||
} else if (Char.charid != CHARID_4_SKELETON && pop_take_hp(1)) {
|
||||
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH); /* HP кончились */
|
||||
} else {
|
||||
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||
hurt_stand_on_floor();
|
||||
}
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
Char.fall_y = 0;
|
||||
/* seg002:0C1F: у Кида свой звук боли, у соперника свой. */
|
||||
pop_sfx_play((uint8_t)(Char.charid == CHARID_0_KID ? 13 : 12));
|
||||
play_seq();
|
||||
|
||||
@@ -549,6 +549,18 @@ static void determine_col(void)
|
||||
* control(). См. разбор там. */
|
||||
void pop_determine_col(void) __banked { determine_col(); }
|
||||
|
||||
/* Тайл ПОЗАДИ персонажа и расстояние до кромки — наружу, для боевой
|
||||
* половины (guards.c): по ним оригинал выбирает, какой смертью умирает
|
||||
* заколотый — на месте или сброшенным с уступа (seg002, hurt_by_sword).
|
||||
* Обе давно работают от Char, а не от Kid; не хватало только объявлений
|
||||
* (docs/sdlpop_audit.md, находка 12). */
|
||||
uint8_t pop_tile_behind_char(void) __banked { return get_tile_behind_char(); }
|
||||
int8_t pop_dist_to_edge_weight(void) __banked
|
||||
{
|
||||
int d = distance_to_edge_weight();
|
||||
return (int8_t)(d > 127 ? 127 : (d < -128 ? -128 : d));
|
||||
}
|
||||
|
||||
/* расстояние до края тайла (для in_wall). */
|
||||
static int distance_to_edge(int xpos)
|
||||
{
|
||||
@@ -682,6 +694,19 @@ static void land(void)
|
||||
* control_with_sword и до него не доходит — Кид садится в присед
|
||||
* НАВСЕГДА (BUG-LAND-SWORD-1). */
|
||||
is_screaming = 0; /* seg005:116 */
|
||||
/* seg005:0173 — ВСЯ развязка приземления (мягко/средне/разбиться) у
|
||||
* оригинала заперта за `if (Char.alive < 0)`, то есть выполняется
|
||||
* только для ЖИВОГО. Мёртвому телу отведена своя дорога (seg005 ветка
|
||||
* else, loc_5F6C): добить HP, звук падения насмерть и seq_22.
|
||||
*
|
||||
* Развилки у нас не было, и труп шёл по живой дороге. Смертельно это
|
||||
* не выглядело только из-за высоты: тело, сброшенное ударом с ОДНОГО
|
||||
* ряда, набирает fall_y < 22 — урона нет, «последнее HP» не тратится,
|
||||
* ветка «разбился» не выбирается никогда, и мёртвый Кид приземлялся
|
||||
* в ПРИСЕД (кадр 109, seq_17). Отсюда симптом «убитого Кида уронили,
|
||||
* а он сел этажом ниже, и только потом обнаружилось, что он мёртв».
|
||||
* Цена проверки — один тест байта на приземление. */
|
||||
if (Char.alive >= 0) goto crushed;
|
||||
if (Char.fall_y < 22) {
|
||||
soft_land:
|
||||
if (Char.charid >= CHARID_2_GUARD || Char.sword == SWORD_2_DRAWN) {
|
||||
@@ -698,6 +723,10 @@ static void land(void)
|
||||
if (!deadly && Char.charid == CHARID_2_GUARD) deadly = 1; /* seg005:190 */
|
||||
Char.fall_x = Char.fall_y = 0;
|
||||
if (deadly || pop_take_hp(1)) { /* 3+ этажа или последнее HP */
|
||||
crushed:
|
||||
/* Сюда же приходит УЖЕ мёртвое тело (см. развилку выше); для
|
||||
* него обнуление ниже — первое, живой путь его уже сделал. */
|
||||
Char.fall_x = Char.fall_y = 0;
|
||||
pop_take_hp(100);
|
||||
if (Char.charid == CHARID_0_KID) pop_sfx_play(0); /* разбился */
|
||||
pop_char_set_seq(SEQ_22_CRUSHED);
|
||||
|
||||
@@ -67,6 +67,11 @@ void pop_row_tiles(int8_t row, int8_t c0, int8_t c1, uint8_t *out) __banked;
|
||||
* pop_load_fram_det_col (pop_kid.c), порт load_fram_det_col. */
|
||||
void pop_determine_col(void) __banked;
|
||||
|
||||
/* Тайл позади АКТИВНОГО персонажа и расстояние до кромки его тайла.
|
||||
* Нужны боевой половине (guards.c) для выбора смерти у обрыва. */
|
||||
uint8_t pop_tile_behind_char(void) __banked;
|
||||
int8_t pop_dist_to_edge_weight(void) __banked;
|
||||
|
||||
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
|
||||
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
|
||||
* респавн). */
|
||||
|
||||
@@ -391,6 +391,15 @@ int main(void)
|
||||
if (!is_demo && pop_app_state != POP_APP_PLAYING) break;
|
||||
demo_new_game = 0;
|
||||
demo_finished = 0;
|
||||
/* Счётчик кадров смерти живёт СНАРУЖИ витка, поэтому переживает возврат
|
||||
* на заставку. Ответ игрока кнопкой его обнуляет, а вот выход по
|
||||
* таймауту (24 с молчания -> title, pop_dead_prompt вернул 2) уходит
|
||||
* мимо этого сброса. Без строки ниже счётчик оставался израсходованным
|
||||
* на всю сессию, и в следующей игре ПЕРВАЯ же смерть мгновенно уводила
|
||||
* в title, не показав «Press Button to Continue». Сбрасываем на входе
|
||||
* в игровой маршрут — это закрывает и любую другую боковую дорогу
|
||||
* (выпадение за нижнюю границу, смена уровня), а не только таймаут. */
|
||||
dead_frames = 0;
|
||||
|
||||
while (!quit && !pop_quit_req && (pop_app_state == POP_APP_PLAYING ||
|
||||
pop_app_state == POP_APP_DEMO)) {
|
||||
|
||||
Reference in New Issue
Block a user