applications/PoP/roomtest: K4 — зацеп/вис/подтягивание/спуск

Порт SDLPoP seg004/005/006 «hang state»:

- pop_map: check_grab (зацеп за уступ в падении по Shift → seq_15 → вис),
  can_grab/can_grab_front_above + tile-запросы над/за персонажем;
  pop_jump_up_seq (check_jump_up: ↑ в стойке = чистый прыжок seq_28/14
  ЛИБО прыжок-с-зацепом seq_8/24/16 за уступ выше → запрыгнуть на этаж);
  pop_hang_* (climb_up seq_10/73, hang_fall seq_23/11, hang-у-стены);
  pop_down_action (спуск seq_68 у края лицом от края / отступ / присед).
- pop_ctrl: control_hanging/can_climb_up/hang_fall, control_jumpup,
  jump_up через pop_jump_up_seq, down_pressed через pop_down_action;
  pop_ctrl_shift_held() для check_grab.

Ключевой фикс check_bumped (seg004 guard'ы): не бампить при action
hang_climb/hang_straight, на кадрах подъёма/спуска 135..148 И на кадрах
виса 87..99.  Без последнего спуск (seq_68) на кадре frame_91 (action
ещё midair, act(hang_climb) идёт следующим опкодом) ловил отскок у стены
и рвал цепочку hang→hang_fall→seq_11, приземляя не туда.

Проверено в MAME (HDD-тест, покадровая трасса через мост): прыжок-с-
зацепом [row2 col4]→вис→подтягивание→[row1 col3]; спуск [row1 col3]→
[row2 col4] с корректной позицией у стены (x=122, совпадает с SDLPoP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 21:07:20 +03:00
parent cd8d566d82
commit ea57a03543
4 changed files with 307 additions and 22 deletions
+237 -8
View File
@@ -18,9 +18,15 @@
#include <stdint.h>
#include "pop_kid.h"
#include "pop_map.h"
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
#define TILE_EMPTY 0
#define TILE_GATE 4
#define TILE_DOORTOP_FLOOR 7 /* tapestry (пол сверху) */
#define TILE_DOORTOP 12 /* tapestry top */
#define TILE_MIRROR 13
#define TILE_CHOMPER 18
#define TILE_WALL 20
#define ACT_STAND 0
#define ACT_RUNJUMP 1
@@ -37,6 +43,18 @@
#define SEQ_20_MEDIUM_LAND 20
#define SEQ_21_FALL_RUNJUMP 21
#define SEQ_47_BUMP 47 /* удар в стену: dx(-4) отскок + 50-52 + stand */
/* K4 — зацеп/вис/подтягивание/спуск */
#define SEQ_8_GRAB_STRAIGHT 8 /* прыжок вверх и зацеп (прямо) */
#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */
#define SEQ_16_JUMP_UP_AND_GRAB 16 /* прыжок вверх и зацеп (нет пола сзади)*/
#define SEQ_24_GRAB_FORWARD 24 /* прыжок вверх и зацеп (пол сзади) */
#define SEQ_28_JUMP_NOTHING_ABOVE 28 /* прыжок вверх: сверху пусто */
#define SEQ_10_CLIMB_UP 10 /* подтягивание из виса */
#define SEQ_11_RELEASE_LEDGE_LAND 11 /* сорваться и встать (конец спуска) */
#define SEQ_15_GRAB_LEDGE_MIDAIR 15 /* зацеп за уступ в падении */
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
#define FRAME_NEEDS_FLOOR 0x40
#define FRAME_WEIGHT_X 0x1F
@@ -52,6 +70,7 @@
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */
static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */
static const int16_t x_bump[20] = {
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
@@ -66,6 +85,7 @@ uint8_t pop_fell_out; /* 1 = Kid упал за низ комнаты
static const uint8_t *g_fg; /* fg[30], row*10+col */
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */
void pop_map_set(const uint8_t *fg) { g_fg = fg; }
@@ -100,6 +120,30 @@ static uint8_t get_tile_infrontof_char(void)
return get_tile(g_infrontx, Kid.curr_row);
}
/* ---- Тайлы НАД/ЗА персонажем (для зацепа/спуска, seg006) ----------- */
static uint8_t get_tile_above_char(void)
{
return get_tile(Kid.curr_col, (int8_t)(Kid.curr_row - 1));
}
static uint8_t get_tile_behind_char(void)
{
return get_tile((int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col),
Kid.curr_row);
}
static uint8_t get_tile_front_above_char(void)
{
g_infrontx = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col);
return get_tile(g_infrontx, (int8_t)(Kid.curr_row - 1));
}
static uint8_t get_tile_behind_above_char(void)
{
return get_tile((int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col),
(int8_t)(Kid.curr_row - 1));
}
static uint8_t wall_type(uint8_t t)
{
switch (t) {
@@ -233,12 +277,59 @@ static void start_fall(void)
in_wall();
}
/* ---- Зацеп (seg006 can_grab/check_grab) --------------------------- *
* can_grab: можно ли зацепиться за g_curr_tile (curr_tile2) СКВОЗЬ through-
* тайл? Kid-only (без loose/opener-модификаторов). */
static uint8_t can_grab(uint8_t through)
{
if (through == TILE_WALL) return 0; /* сквозь стену нельзя */
if (through == TILE_DOORTOP && Kid.direction >= 0) return 0; /* верх двери, лицом вправо */
if (tile_is_floor(through)) return 0; /* сквозь пол нельзя */
if (g_curr_tile == TILE_DOORTOP_FLOOR && Kid.direction < 0) return 0;
if (!tile_is_floor(g_curr_tile)) return 0; /* не за что цепляться */
return 1;
}
static uint8_t can_grab_front_above(void)
{
uint8_t through = get_tile_above_char();
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
return can_grab(through);
}
/* check_grab (seg006:0A28): в падении при зажатом Shift — зацепиться за
* уступ спереди-сверху (seq_15), если скорость падения ещё мала и высота
* подходящая. Выравнивает передний край по кромке, гасит fall_y, взводит
* grab_timer (блок climb-up на несколько кадров). */
static void check_grab(void)
{
uint8_t old_x;
if (!pop_ctrl_shift_held()) return; /* Shift не зажат */
if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */
if ((uint16_t)y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return;
old_x = Kid.x;
Kid.x = (uint8_t)char_dx_forward(-8);
determine_col();
if (!can_grab_front_above()) {
Kid.x = old_x; /* не за что — назад */
determine_col();
} else {
Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight());
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
Kid.fall_y = 0;
kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR);
play_seq();
determine_col();
grab_timer = 12; /* (TODO: sound grab) */
}
}
static void do_fall(void)
{
uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
if (nrow > 4) nrow = 4; /* защита y_land[] от выхода */
if ((uint16_t)y_land[nrow] > (uint16_t)Kid.y) {
/* ещё летит (check_grab — K4) */
check_grab(); /* ещё летит — попытка зацепа */
} else {
if (get_tile_at_char() == TILE_WALL)
in_wall();
@@ -306,13 +397,68 @@ int pop_edge_distance(void)
uint8_t pop_edge_type(void) { return g_edge_type; }
/* Тайл НАД персонажем (curr_row-1, curr_col) сплошной (стена/пол)? — выбор
* seq прыжка вверх: solid -> seq_14 (в потолок), иначе seq_28 (пусто сверху).
* K4.1 упрощение: без grab-логики (уступ сверху-впереди) — та в K4.2. */
uint8_t pop_above_solid(void)
/* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- *
* pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в
* пустоту / seq_14 в потолок) ЛИБО прыжок-и-зацеп за уступ выше (seq_8/
* 24 — с полом сзади, seq_16 — без), и выравнивает Kid.x. control_up
* гасит caller (pop_ctrl). Порт: check_jump_up/grab_up_with_floor_behind/
* jump_up_or_grab/grab_up_no_floor_behind/jump_up (без super-high-jump). */
static uint8_t grab_up_with_floor_behind(void)
{
uint8_t t = get_tile(Kid.curr_col, (int8_t)(Kid.curr_row - 1));
return (t == TILE_WALL || tile_is_floor(t));
int distance = distance_to_edge_weight();
int edge_distance = pop_edge_distance(); /* побочно ставит g_edge_type */
if (distance < 4 && edge_distance < 4 && g_edge_type != EDGE_WALL) {
Kid.x = (uint8_t)char_dx_forward((int8_t)distance);
return SEQ_8_GRAB_STRAIGHT;
}
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - 4));
return SEQ_24_GRAB_FORWARD;
}
static uint8_t jump_up_plain(void) /* seg005 jump_up (без super) */
{
int distance = pop_edge_distance(); /* ставит g_edge_type */
int8_t col;
uint8_t t;
if (distance < 4 && g_edge_type == EDGE_WALL)
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - 3));
col = get_tile_div_mod(dx_weight() - 6);
t = get_tile(col, (int8_t)(Kid.curr_row - 1));
if (t != TILE_WALL && !tile_is_floor(t))
return SEQ_28_JUMP_NOTHING_ABOVE;
return SEQ_14_JUMP_INTO_CEILING;
}
static uint8_t grab_up_no_floor_behind(void)
{
get_tile_above_char();
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance_to_edge_weight() - 10));
return SEQ_16_JUMP_UP_AND_GRAB;
}
static uint8_t jump_up_or_grab(void)
{
int distance = distance_to_edge_weight();
if (distance < 6)
return jump_up_plain();
if (!tile_is_floor(get_tile_behind_char()))
return grab_up_no_floor_behind();
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance - TILE_SIZEX));
determine_col(); /* load_fram_det_col */
return grab_up_with_floor_behind();
}
uint8_t pop_jump_up_seq(void)
{
uint8_t through = get_tile_above_char();
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
if (can_grab(through))
return grab_up_with_floor_behind();
through = get_tile_behind_above_char();
get_tile_above_char(); /* g_curr_tile = сверху */
if (can_grab(through))
return jump_up_or_grab();
return jump_up_plain();
}
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
@@ -323,6 +469,78 @@ int pop_wall_ahead(void)
return (g_edge_type == EDGE_WALL && d <= 2);
}
/* ---- Вис (control_hanging из seg005) — тайловые решения ------------ *
* Управление держится в pop_ctrl (control_*), сюда вынесены только
* тайловые предикаты + выбор seq (+ выравнивание Kid.x). */
/* grab_timer истёк — можно подтягиваться? */
uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; }
/* can_climb_up: тайл над персонажем — обычный подъём (seq_10) либо подъём
* к закрытой калитке/зеркалу/чомперу (seq_73). */
uint8_t pop_hang_climb_up_seq(void)
{
uint8_t t = get_tile_above_char();
if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) ||
(t == TILE_GATE && Kid.direction != 0))
return SEQ_73_CLIMB_UP_TO_GATE;
return SEQ_10_CLIMB_UP;
}
/* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 =
* сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */
int8_t pop_hang_wall(void)
{
uint8_t t = get_tile_at_char();
if (Kid.action != ACT_HANGSTRAIGHT &&
(t == TILE_WALL ||
(Kid.direction < 0 && (t == TILE_DOORTOP_FLOOR || t == TILE_DOORTOP))))
return 1;
if (!tile_is_floor(get_tile_above_char()))
return -1;
return 0;
}
/* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под
* персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7)
* и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */
uint8_t pop_hang_fall_seq(void)
{
uint8_t behind = get_tile_behind_char();
uint8_t at;
if (!tile_is_floor(behind) && !tile_is_floor(get_tile_at_char()))
return SEQ_23_RELEASE_LEDGE_FALL;
at = get_tile_at_char();
if (at == TILE_WALL ||
(Kid.direction < 0 && (at == TILE_DOORTOP_FLOOR || at == TILE_DOORTOP)))
Kid.x = (uint8_t)char_dx_forward(-7);
return SEQ_11_RELEASE_LEDGE_LAND;
}
/* down_pressed (seg005:04C7) tile-часть: 1 = обработано (НЕ приседать —
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
* пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */
uint8_t pop_down_action(void)
{
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */
determine_col();
return 1;
}
if (!tile_is_floor(get_tile_behind_char()) && distance_to_edge_weight() >= 8) {
uint8_t through = get_tile_behind_char();
get_tile_at_char(); /* g_curr_tile = под персонажем */
if (can_grab(through) &&
(Kid.direction >= 0 || get_tile_at_char() != TILE_GATE)) {
Kid.x = (uint8_t)char_dx_forward((int8_t)(distance_to_edge_weight() - 9));
kid_set_seq(SEQ_68_CLIMB_DOWN);
return 1;
}
}
return 0;
}
/* ---- Диспетчер ----------------------------------------------------- */
static void check_on_floor(void)
{
@@ -357,7 +575,17 @@ static void check_action(void)
static void check_bumped(void)
{
uint8_t t; int8_t tcol; int d;
if (Kid.action == ACT_FREEFALL || Kid.action == ACT_HANGCLIMB) return;
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
* в падении держит do_fall/in_wall. */
if (Kid.action == ACT_FREEFALL || Kid.action == ACT_HANGCLIMB ||
Kid.action == ACT_HANGSTRAIGHT) return;
if (Kid.frame >= 135 && Kid.frame < 149) return; /* кадры подъёма/спуска */
/* Кадры виса 87..99: в спуске (seq_68) кадр frame_91 идёт ДО act(hang_climb),
* т.е. action ещё midair — без этого guard'а bump у стены рвёт цепочку
* hang->hang_fall->seq_11 (правильное приземление). */
if (Kid.frame >= 87 && Kid.frame < 100) return;
t = get_tile_at_char(); tcol = Kid.curr_col;
if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; }
if (!wall_type(t)) return;
@@ -374,6 +602,7 @@ static void check_bumped(void)
void pop_phys_tick(void)
{
if (grab_timer) grab_timer--;
fall_accel();
fall_speed();
determine_col();