L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях

Порт SDLPoP:
  animate_chomper / start_chompers / start_anim_chomper /
  next_chomper_timing (seg007) -> pop_trob.c;
  draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) ->
  pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой);
  check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c.

Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N,
старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда).
Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c):
их читают обе половины слоя фона, а const-таблица банка из чужого банка
не видна.

start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq
(pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c).
Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале.

check_chomped_guard у оригинала отдельное тело (страж не проходит через
check_collisions).  У нас та же формула уже есть в get_row_collision_data,
поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below —
это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание
сломало бы ему бамп.

Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале).

Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked,
хотя функция давно переехала в резидент — всплыло при пересборке.

Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07).  Банк 6: 20.0 %,
банк 7: 39.7 %, банк 3: 53.8 %.  tests-host зелёные (5 наборов).
Зубья в MAME рисуются; анимация и смерть — на ручной приёмке.
This commit is contained in:
2026-08-08 19:05:16 +03:00
parent 4310f94795
commit dc0bd47368
14 changed files with 238 additions and 3 deletions
+62 -2
View File
@@ -90,6 +90,7 @@
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
#define SEQ_54_CHOMPED 54 /* попасть в челюсти чомпера (смерть) */
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
#define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */
#define SEQ_65_BUMP_FWD_SWORD 65 /* стража прижали к стене (с мечом) */
@@ -99,6 +100,7 @@
#define TILE_FLOOR 1
#define FRAME_109_CROUCH 109
#define FRAME_178_CHOMPED 178
/* Кадры анимации для check_spiked (seg006:0658). */
#define FRAME_7_RUN 7
@@ -521,6 +523,7 @@ static void land(void)
/* к краю пола — чуть назад (как оригинал) */
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
Char.x = (uint8_t)char_dx_forward(-3);
pop_start_chompers(Char.room, Char.curr_row); /* seg005:0148 */
/* Урон от падения (seg005:173) — ветвление по ПЕРСОНАЖУ, а не только по
* высоте. Порт построчно:
* 1 этаж (fall_y < 22): не-Кид (charid >= guard) ИЛИ меч вынут →
@@ -575,7 +578,7 @@ static void start_fall(void)
* control_with_sword разбирает кадры падения как боевые. */
Char.sword = SWORD_0_SHEATHED;
inc_curr_row();
/* start_chompers() — чомперов ещё нет (L3-CHOMP). */
pop_start_chompers(Char.room, Char.curr_row); /* seg006:08C1 */
/* seg006:1044 start_fall: frame 9 -> seq_7, 13 -> seq_19 (лишний dx(1)) */
if (frame == 13) seq = SEQ_19_FALL;
else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP;
@@ -2107,6 +2110,61 @@ static void check_spike_below(void)
}
}
/* chomped (seg004:07BF): персонажа перемололо. Тайл помечается кровью
* (0x80 в modif), персонаж выравнивается по колонке чомпера, теряет все HP
* и уходит в seq_54. Условие «кадр ещё не 178» — чтобы не перезапускать
* последовательность каждый кадр, пока труп лежит в челюстях.
*
* Кровь пишется ДАЖЕ у скелета: SDLPoP чинит это опцией
* fix_skeleton_chomper_blood, но в ВАНИЛИ она есть, и мы держим ваниль. */
static void chomped(uint8_t tilepos, int8_t col)
{
pop_trob_modif(g_room)[tilepos] |= 0x80;
if (Char.frame == FRAME_178_CHOMPED) return;
Char.x = (uint8_t)(pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX);
Char.x = (uint8_t)char_dx_forward(7 - !Char.direction);
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
pop_take_hp(100); /* (TODO: sound_46_chomped) */
pop_char_set_seq(SEQ_54_CHOMPED);
play_seq();
if (Char.charid == CHARID_0_KID) pop_kid_dead = 1;
}
/* check_chomped_kid (seg004:076B): по колонкам ряда — там, где персонаж
* перекрывает тайл ОБЕИМИ гранями (у оригинала curr_row_coll_flags == 0xFF,
* у нас оба бита) и этот тайл — СОМКНУТЫЙ чомпер (modif & 0x7F == 2),
* персонажа перемалывает. */
static void check_chomped_flags(const uint8_t *flags)
{
int8_t row = Char.curr_row, col;
uint8_t tp;
if (row < 0 || row > 2) return;
for (col = 0; col <= 9; col++) {
if (flags[COLL_IDX(col)] != 3) continue;
if (get_tile(col, row) != TILE_CHOMPER) continue;
tp = (uint8_t)(row * 10 + col);
if ((pop_trob_modif(g_room)[tp] & 0x7F) == 2) chomped(tp, col);
}
}
static void check_chomped_kid(void) { check_chomped_flags(coll_curr); }
/* check_chomped_guard (seg004:0989) — у оригинала ОТДЕЛЬНОЕ тело: страж не
* проходит через check_collisions, поэтому там перекрытие считается прямо по
* координатам граней (check_chomped_here). У нас та же формула уже живёт в
* get_row_collision_data, поэтому считаем флаги ряда во ВРЕМЕННЫЙ массив и
* идём общим телом. Именно во временный: coll_curr/above/below — это кадр
* КИДА, из них move_coll_to_prev берёт «прошлые» флаги на следующем кадре, и
* затирание сломало бы ему бамп. */
static void check_chomped_guard(void)
{
uint8_t flags[COLL_N];
if (Char.curr_row < 0 || Char.curr_row > 2) return;
set_char_collision();
get_row_collision_data(Char.curr_row, flags);
check_chomped_flags(flags);
}
/* ---- Кадровая физика: одна и та же над Char, два входа --------------- *
* Оригинал держит физику ОДНИМ набором функций над Char, а кто именно в
* Char — решают окна loadkid/savekid и loadshad/saveshad (seg006:809).
@@ -2126,6 +2184,7 @@ static void kid_phys(void)
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */
if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
/* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой
@@ -2144,7 +2203,7 @@ static void kid_phys(void)
/* play_guard_frame (seg000:1252..1266). Чего у стража НЕТ по сравнению с
* Кидом: bump_into_opponent, check_collisions/check_bumped (у него свой
* check_guard_bumped), check_gate_push, check_knock и весь уход из комнаты.
* check_chomped_guard появится вместе с чомперами (L3-CHOMP). */
* check_chomped_guard — то же тело, что у Кида (у нас check_chomped). */
static void guard_phys(void)
{
fall_accel();
@@ -2155,6 +2214,7 @@ static void guard_phys(void)
check_press(); /* страж тоже давит кнопки и проваливает loose */
check_spike_below();
check_spiked();
check_chomped_guard();/* seg000:1265 */
}
/* Физика КИДА — своё окно Char: главный цикл зовёт нас вне окон, а физика