/*
 * pop_map.c — коллизия Kid с картой (Фаза K3.2: падение).  Порт SDLPoP
 * seg005/seg006.  Пер-кадровый physics-диспетч check_action() по Char.action:
 *   grounded (stand/run/step/crouch) -> check_on_floor(): кадр с флагом
 *     FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall();
 *     стена -> in_wall();
 *   freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed),
 *     достиг y_land[curr_row+1] -> land()/inc_curr_row.
 *
 * Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX);
 * col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 =
 * div_mod(xpos-7).  Карта: fg[row*10+col] & 0x1F; col<0/>9,row<0/>2 = стена.
 *
 * MVP: без spike/HP/grab/gate — падение + мягкое/среднее приземление.
 * Бег В стену (check_bumped) и осторожный шаг у края (get_edge_distance) —
 * следующим шагом.
 */
#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
#define ACT_HANGCLIMB     2
#define ACT_MIDAIR        3
#define ACT_FREEFALL      4
#define ACT_BUMPED        5
#define ACT_HANGSTRAIGHT  6

#define SEQ_7_FALL              7   /* stepfall  (stand/run/step/crouch) */
#define SEQ_17_SOFT_LAND       17
#define SEQ_18_FALL_STANDJUMP  18
#define SEQ_19_FALL            19   /* stepfall2 (run frames 9/13)       */
#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
#define TILE_SIZEX             14
#define TILE_RIGHTX           13
#define SCREENSPACE_X         58
#define FALL_ACCEL             3
#define FALL_MAX             33

#define TILE_MIDX              7
#define FIRST_ONSCREEN_COLUMN  5
#define FRAME_THIN          0x20

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
};
/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */
static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0};   /* wall_dist_from_left */
static const int8_t wall_dr[6] = {0,  0, 10, 13, 0, 0};  /* wall_dist_from_right */
static int8_t g_infrontx;      /* колонка «перед персонажем» (set в get_tile_infrontof) */

/* ---- Карта комнаты ------------------------------------------------- */
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; }

static uint8_t get_tile(int8_t col, int8_t row)
{
    if (col < 0 || col > 9 || row < 0 || row > 2)
        g_curr_tile = TILE_WALL;            /* край уровня = стена */
    else
        g_curr_tile = g_fg[row * 10 + col] & 0x1F;
    return g_curr_tile;
}

static uint8_t tile_is_floor(uint8_t t)
{
    switch (t) {
    case 0: case 9: case 12: case 20:
    case 26: case 27: case 28: case 29:
        return 0;
    default:
        return 1;
    }
}

static uint8_t get_tile_at_char(void)
{
    return get_tile(Kid.curr_col, Kid.curr_row);
}

static uint8_t get_tile_infrontof_char(void)
{
    g_infrontx = (int8_t)(dir_front[Kid.direction + 1] + Kid.curr_col);
    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) {
    case 4: case 7: case 12: return 1;   /* стена справа (gate/doortop) */
    case 13: return 2;                    /* стена слева (mirror)        */
    case 18: return 3;                    /* chomper                     */
    case 20: return 4;                    /* стена с обеих сторон        */
    default: return 0;
    }
}

/* ---- Координаты ---------------------------------------------------- */
static int char_dx_forward(int8_t dx)
{
    int d = dx;
    if (Kid.direction < 0) d = -d;
    return (int)Kid.x + d;
}

/* col = (xpos-58)/14 округл. вниз; g_obj_xl = позиция в тайле (0..13). */
static int8_t get_tile_div_mod(int xpos)
{
    int x = xpos - SCREENSPACE_X;
    int xh = x / TILE_SIZEX;
    int xl = x % TILE_SIZEX;
    if (xl < 0) { --xh; xl += TILE_SIZEX; }   /* округление вниз */
    g_obj_xl = (uint8_t)xl;
    return (int8_t)xh;
}

static int8_t get_tile_div_mod_m7(int xpos) { return get_tile_div_mod(xpos - 7); }

/* «весовая» точка кадра (dx с вычетом weight-битов флагов), в направлении. */
static int dx_weight(void)
{
    int8_t offset = (int8_t)(kid_cur_dx() - (kid_cur_flags() & FRAME_WEIGHT_X));
    return char_dx_forward(offset);
}

static void determine_col(void)
{
    Kid.curr_col = get_tile_div_mod_m7(dx_weight());
}

/* расстояние до края тайла (для in_wall). */
static int distance_to_edge(int xpos)
{
    int d;
    get_tile_div_mod_m7(xpos);           /* -> g_obj_xl */
    d = g_obj_xl;
    if (Kid.direction == 0) d = TILE_RIGHTX - d;   /* dir_0_right */
    return d;
}
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }

/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
 * Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx)
 * (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
 * THIN-кадров.  dist_from_wall_forward — расстояние переднего края до грани
 * стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
static int char_front_coll(void)
{
    int cx = char_dx_forward(kid_cur_dx());
    if (kid_cur_flags() & FRAME_THIN)
        cx += (Kid.direction < 0) ? 4 : -4;    /* THIN: край внутрь */
    return cx;
}

static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol)
{
    uint8_t type = wall_type(tiletype);
    int coll_left, cx;
    if (type == 0) return 127;
    coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
    cx = char_front_coll();
    if (Kid.direction < 0)                     /* лицом влево */
        return cx - (coll_left + TILE_RIGHTX - wall_dr[type]);
    else                                       /* лицом вправо */
        return wall_dl[type] + coll_left - cx;
}

/* Выровнять передний край РОВНО к грани стены, если он зашёл за неё (bumped:
 * Char.x += dist; char_dx_forward отражает знак для обоих направлений).
 * Стена ищется под персонажем, иначе перед ним. */
static void in_wall(void)
{
    uint8_t t; int8_t tcol; int d;
    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;
    d = dist_from_wall_forward(t, tcol);
    if (d < 0) {
        Kid.x = (uint8_t)char_dx_forward((int8_t)d);   /* край -> грань */
        determine_col();
        get_tile_at_char();
    }
}

/* ---- Падение ------------------------------------------------------- */
static void inc_curr_row(void) { Kid.curr_row++; }

static void land(void)
{
    uint8_t seq;
    Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
    /* к краю пола — чуть назад (как оригинал) */
    if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
        Kid.x = (uint8_t)char_dx_forward(-3);
    if (Kid.fall_y < 22)
        seq = SEQ_17_SOFT_LAND;          /* 1 этаж — мягко */
    else
        seq = SEQ_20_MEDIUM_LAND;        /* 2+ этажа — присесть (HP/смерть — позже) */
    Kid.fall_x = Kid.fall_y = 0;
    kid_set_seq(seq);
    play_seq();
    determine_col();
}

static void start_fall(void)
{
    uint8_t frame = Kid.frame, seq;
    inc_curr_row();
    if (frame == 9 || frame == 13) seq = SEQ_19_FALL;   /* падение из бега */
    else if (frame == 26)          seq = SEQ_18_FALL_STANDJUMP;
    else if (frame == 44)          seq = SEQ_21_FALL_RUNJUMP;
    else                           seq = SEQ_7_FALL;    /* stand/step/crouch */
    kid_set_seq(seq);
    play_seq();
    determine_col();
    if (get_tile_at_char() == TILE_WALL)
        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();                         /* ещё летит — попытка зацепа */
    } else {
        if (get_tile_at_char() == TILE_WALL)
            in_wall();
        if (tile_is_floor(g_curr_tile))
            land();
        else if (Kid.curr_row < 2)
            inc_curr_row();                   /* следующий ряд ВНУТРИ комнаты */
        else
            pop_fell_out = 1;                 /* упал за низ комнаты (нет пола,
                                                 комнаты снизу нет) — приложение
                                                 сбросит Kid (K-later: переход) */
    }
}

static void fall_accel(void)
{
    if (Kid.action == ACT_FREEFALL) {
        int fy = (uint8_t)Kid.fall_y + FALL_ACCEL;
        if (fy > FALL_MAX) fy = FALL_MAX;
        Kid.fall_y = (int8_t)fy;
    }
}

static void fall_speed(void)
{
    Kid.y = (uint8_t)(Kid.y + (uint8_t)Kid.fall_y);
    if (Kid.action == ACT_FREEFALL) {
        Kid.x = (uint8_t)char_dx_forward(Kid.fall_x);
        determine_col();
    }
}

/* ---- get_edge_distance (seg004:067C) — ТОЧНО ---------------------- *
 * Расстояние переднего края до стены/края впереди + тип g_edge_type: WALL
 * (стена в пределах тайла), FLOOR (пол — далеко, 11), CLOSER (край уступа
 * над ямой — distance_to_edge_weight).  Стена сперва под персонажем, потом
 * перед ним; dist<=TILE_RIGHTX -> WALL, иначе FLOOR. */
static uint8_t g_edge_type;

static int edge_classify(int d)
{
    if (d <= TILE_RIGHTX) g_edge_type = EDGE_WALL;
    else { g_edge_type = EDGE_FLOOR; d = 11; }
    return d;
}

int pop_edge_distance(void)
{
    uint8_t t; int d;
    determine_col();
    t = get_tile_at_char();
    if (wall_type(t)) {
        d = dist_from_wall_forward(t, Kid.curr_col);
        if (d >= 0) return edge_classify(d);
    }
    t = get_tile_infrontof_char();
    if (wall_type(t)) {
        d = dist_from_wall_forward(t, g_infrontx);
        if (d >= 0) return edge_classify(d);
    }
    if (tile_is_floor(t)) { g_edge_type = EDGE_FLOOR; return 11; }
    g_edge_type = EDGE_CLOSER;             /* яма впереди — до кромки уступа */
    return distance_to_edge_weight();
}

uint8_t pop_edge_type(void) { return g_edge_type; }

/* ---- Прыжок вверх с зацепом (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)
{
    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();
}

/* Стена впереди достаточно близко для стопа бега?  Детект (без сдвига —
 * позицию держит check_bumped=in_wall).  d<=2: передний край почти у грани. */
int pop_wall_ahead(void)
{
    int d = pop_edge_distance();
    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)
{
    if (kid_cur_flags() & FRAME_NEEDS_FLOOR) {
        if (get_tile_at_char() == TILE_WALL)
            in_wall();                        /* забежал в стену -> выровнять */
        if (!tile_is_floor(g_curr_tile))
            start_fall();
    }
}

static void check_action(void)
{
    uint8_t action = Kid.action, frame = Kid.frame;
    if (action == ACT_BUMPED || action == ACT_HANGSTRAIGHT) {
        if (frame == 109 || (frame >= 110 && frame <= 119))
            check_on_floor();
    } else if (action == ACT_FREEFALL) {
        do_fall();
    } else if (action == ACT_MIDAIR) {
        /* frames 102..105: check_grab — K4 */
    } else if (action != ACT_HANGCLIMB) {
        check_on_floor();
    }
}

/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
 * к грани И проиграть отскок seq_47_bump (dx−4 назад + кадры 50-52 + stand),
 * как в оригинале (bumped_floor).  Это даёт «шаг назад со звуком» при беге/
 * шаге в стену вместо падения.  Уже в отскоке (action bumped) — только
 * выравниваем, без рестарта.  В падении/висе не трогаем. */
static void check_bumped(void)
{
    uint8_t t; int8_t tcol; int d;
    /* Не бампим в падении/висе/подтягивании (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 || Kid.action == ACT_MIDAIR) 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;
    /* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а
     * dist_from_wall_forward/x_bump[] — мусор с большим отриц. сдвигом →
     * underflow X и уход в стену.  fell_out ловит do_fall. */
    if (Kid.curr_row < 0 || Kid.curr_row > 2) 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;
    d = dist_from_wall_forward(t, tcol);
    if (d >= 0) return;                    /* ещё не зашёл за грань */
    Kid.x = (uint8_t)char_dx_forward((int8_t)d);   /* край -> грань */
    determine_col();
    if (Kid.action == ACT_BUMPED) return;  /* уже в отскоке — не рестартить */
    Kid.fall_y = 0;
    kid_set_seq(SEQ_47_BUMP);              /* отскок (TODO: bumped_sound) */
    play_seq();
    determine_col();
}

void pop_phys_tick(void)
{
    if (grab_timer) grab_timer--;
    fall_accel();
    fall_speed();
    determine_col();
    check_bumped();       /* удержать у стены до check_action (порядок PoP) */
    check_action();
}
