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
+81
View File
@@ -15,6 +15,7 @@
* нельзя звать из __banked-кода. */
#include "pop_bg.h"
#include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */
#include "pop_kid.h" /* Kid.curr_row/alive — условие остановки чомпера */
#include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */
/* Коды тайлов (fg & 0x1F), которые анимируются. */
@@ -27,6 +28,7 @@
#define TILE_LOOSE 0x0B /* проваливающийся пол (фаза тряски в modif) */
#define TILE_SWORD 0x16 /* меч-предмет на полу */
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
#define TILE_CHOMP 0x12 /* чомпер (челюсти) */
#define TILE_TORCH 0x13
#define TILE_TORCH_DEBRIS 0x1E
@@ -196,6 +198,75 @@ static uint8_t bubble_next_frame(uint8_t curr)
return (next >= 8) ? 1 : next;
}
/* animate_chomper (seg007:0448). Модификатор: младшие 7 бит — фаза 1..N,
* старший — «перемололо кого-то» (кровь). Фаза крутится по кругу до
* POP_CHOMPER_SPEED (у оригинала 15), то есть челюсти смыкаются раз в N
* кадров; фаза 2 — момент удара (в оригинале там звук sound_47).
*
* Остановка (иначе чомперы щёлкали бы всю игру): если Кид ушёл из этой
* комнаты, или ушёл из этого РЯДА, или умер не в этом чомпере — trob
* снимается, но только после фазы 6, чтобы челюсти успели разомкнуться.
* Запускает их обратно start_chompers на смене ряда. Аргументы room/tp
* нужны ровно для этой проверки. */
static void animate_chomper(uint8_t *m, int8_t *type,
uint8_t room, uint8_t tp, uint8_t drawn_room)
{
uint8_t blood, frame;
if (*type >= 0) {
blood = (uint8_t)(*m & 0x80);
frame = (uint8_t)((*m & 0x7F) + 1);
if (frame > POP_CHOMPER_SPEED) frame = 1;
*m = (uint8_t)(blood | frame);
if ((room != drawn_room || tp / 10 != Kid.curr_row ||
(Kid.alive >= 0 && blood == 0)) && frame >= 6)
*type = -1;
}
}
/* next_chomper_timing (seg007:0F9A): 15,12,9,6,13,10,7,14,11,8 по кругу —
* чомперы одного ряда стартуют вразнобой, а не хлопают в унисон. */
static uint8_t next_chomper_timing(uint8_t timing)
{
timing -= 3;
if (timing < 6) timing += 10;
return timing;
}
/* start_anim_chomper (seg007:08C7): запустить, только если стоит (0) или
* уже прошёл фазу размыкания (>=6). */
static void start_anim_chomper(uint8_t room, uint8_t tp, uint8_t modifier)
{
uint8_t *mod = pop_trob_modif(room);
uint8_t old = (uint8_t)(mod[tp] & 0x7F);
if (old == 0 || old >= 6) {
mod[tp] = modifier;
pop_add_trob(room, tp, 1);
}
}
/* start_chompers (seg007:0F13): все чомперы РЯДА активного персонажа.
* Зовётся отовсюду, где персонаж сменил ряд (SEQ_UP/SEQ_DOWN, start_fall,
* приземление) и на входе в комнату. */
void pop_start_chompers(uint8_t room, int8_t row) __banked
{
uint8_t timing = 15, col, tp;
uint8_t *mod;
if ((uint8_t)row >= 3) return; /* вне комнаты — нечего */
mod = pop_trob_modif(room);
pop_level_access_begin();
for (col = 0; col < 10; col++) {
tp = (uint8_t)(row * 10 + col);
if ((pop_level_tile_raw(room, tp) & 0x1F) == TILE_CHOMP) {
uint8_t m = (uint8_t)(mod[tp] & 0x7F);
if (m == 0 || m >= 6) {
start_anim_chomper(room, tp, (uint8_t)(timing | (mod[tp] & 0x80)));
timing = next_chomper_timing(timing);
}
}
}
pop_level_access_end();
}
static void animate_spike(uint8_t *m, int8_t *type)
{
uint8_t mod = *m;
@@ -429,6 +500,9 @@ void pop_process_trobs(uint8_t cur_room) __banked
case TILE_SPIKE:
animate_spike(&mod[tp], &type);
break;
case TILE_CHOMP:
animate_chomper(&mod[tp], &type, room, tp, cur_room);
break;
case TILE_OPENER:
case TILE_CLOSER:
animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */
@@ -490,6 +564,13 @@ void pop_process_trobs(uint8_t cur_room) __banked
* страницы: живой trob метит каждый кадр по одной. */
pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1));
}
if (room == cur_room && code == TILE_CHOMP) {
/* Кадр меняется каждый тик, пока trob жив — как у пик, метим
* текущую страницу; последний кадр (челюсти встали) — обе,
* иначе на второй странице дабл-буфера застынет предыдущая поза
* и чомпер дрожит через кадр. */
pop_set_redraw(tp, POP_RD_CHOMP, (uint8_t)(type < 0 ? 2 : 1));
}
if (room == cur_room && code == TILE_GATE) {
/* draw_trob (seg007:01E6), которым заканчивается animate_door:
* пока trob жив — решётка едет каждый кадр, помечаем ТЕКУЩУЮ