/*
 * pop_ctrl.c — управление Kid (Фаза K2b).  Порт SDLPoP:
 *   вход           — read_user_control/user_control/rest_ctrl_1/save_ctrl_1
 *                    (seg006.c) поверх held-state <kbd_raw.h>;
 *   диспетчер      — control() + control_standing/turning/startrun/running/
 *                    crouched (seg005.c), Kid-only ветки (без меча/стражи);
 *   действия       — forward/back/up/down_pressed, crouch, standing_jump,
 *                    run_jump, safe_step (seg005.c).
 *
 * Map-запросы (стена/край/захват) ЗАСТАБЛЕНЫ «открытый пол» (map_* ниже) —
 * реальная коллизия в K3/K4.  Поэтому: forward -> всегда бег, down -> присед,
 * back -> разворот, up+forward -> прыжок с места; osторожный шаг и climb
 * помечены TODO(K3/K4).
 *
 * Значения CONTROL_* (types.h): RELEASED=0, IGNORE=1, HELD=-1;
 * X: LEFT/FORWARD=-1, RIGHT/BACKWARD=+1; Y: UP=-1, DOWN=+1.
 */
#include <stdint.h>
#include <kbd_raw.h>
#include "pop_kid.h"
#include "pop_map.h"
#include "pop_guard.h"        /* charid, состояние меча, seq стража */

/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
 * MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо.  PS/2 set2
 * scancodes.  TODO: убрать после отладки. */
#define KBD_DBG_STEPL  0x3B    /* J */
#define KBD_DBG_STEPR  0x4B    /* L */

/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
#define CONTROL_RELEASED       0
#define CONTROL_IGNORE         1
#define CONTROL_HELD          (-1)
#define CONTROL_HELD_LEFT     (-1)
#define CONTROL_HELD_RIGHT     1
#define CONTROL_HELD_FORWARD  (-1)
#define CONTROL_HELD_BACKWARD  1
#define CONTROL_HELD_UP       (-1)
#define CONTROL_HELD_DOWN      1

#define DIR_RIGHT   0        /* dir_0_right  */

#define SEQ_1_START_RUN             1
#define SEQ_2_STAND                 2
#define SEQ_3_STANDING_JUMP         3
#define SEQ_4_RUN_JUMP              4
#define SEQ_5_TURN                  5
#define SEQ_6_RUN_TURN              6
#define SEQ_13_STOP_RUN            13
#define SEQ_26_CROUCH_WHILE_RUNNING 26
#define SEQ_43_START_RUN_AFTER_TURN 43
#define SEQ_49_STAND_UP_FROM_CROUCH 49
#define SEQ_50_CROUCH              50
#define SEQ_79_CROUCH_HOP          79
#define SEQ_78_DRINK               78   /* выпить зелье               */
#define SEQ_91_PICKUP_SWORD        91   /* поднять меч (pickupsword) */
#define SEQ_STEP_BASE              28   /* +distance(1..14) = seq 29..42 шаг-N */
#define SEQ_39_UNSAFE_STEP         39   /* шаг за кромку (distance 11)         */
#define SEQ_44_STEP_ON_EDGE        44
#define SEQ_14_JUMP_INTO_CEILING   14   /* прыжок вверх: сверху стена/пол      */
#define SEQ_28_JUMP_NOTHING_ABOVE  28   /* прыжок вверх: сверху пусто          */
#define SEQ_25_HANG_AGAINST_WALL   25   /* вис у стены (Shift в висе)          */
#define SEQ_GO_UP_LEVELDOOR        70   /* seq_70_go_up_on_level_door          */

#define FRAME_7_RUN         7
#define FRAME_11_RUN       11
#define FRAME_15_STAND     15
#define FRAME_48_TURN      48
#define FRAME_50_TURN      50
#define FRAME_67_JUMPUP1   67
#define FRAME_70_JUMPHANG  70
#define FRAME_87_HANG1     87
#define FRAME_109_CROUCH  109

/* ---- Состояние управления (seg006 globals) ------------------------ *
 * control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно:
 * X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
 * автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
 * кадрами (rest/save). */
int8_t control_x, control_y, control_shift;
/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
 * СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
 * control(), что и у игрока.  Поэтому шина общая. */
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;

/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
 * Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
 * не дублируются в этот TU). */
/* В оригинале seqtbl_offset_char работает с АКТИВНЫМ персонажем, и
 * control() вызывается внутри окна Char.  Раньше макрос вёл на
 * kid_set_seq (пишет прямо в Kid) — после введения окна это ломало ввод:
 * savekid затирал Kid копией Char со старой curr_seq. */
#define seqtbl_offset_char(seq)  pop_char_set_seq(seq)

static int8_t release_arrows(void)
{
    control_backward = control_forward = control_up = control_down = CONTROL_RELEASED;
    return CONTROL_IGNORE;   /* seg006: return 1 */
}

/* Край/стена впереди — из pop_map (get_edge_distance): pop_edge_distance()
 * возвращает дистанцию и ставит pop_edge_type() = EDGE_WALL/FLOOR/CLOSER. */

/* ---- Действия (seg005) -------------------------------------------- */
static void crouch(void)
{
    seqtbl_offset_char(SEQ_50_CROUCH);
    control_down = release_arrows();
}

static void standing_jump(void)
{
    control_up = control_forward = CONTROL_IGNORE;
    seqtbl_offset_char(SEQ_3_STANDING_JUMP);
}

/* check_get_item (seg005:061F): решение по тайлам принимает pop_map, здесь
 * только исполнение.  Триггер в оригинале — Shift (control_shift2) в стойке
 * или в приседе: первый раз Kid приседает к предмету, второй — поднимает.
 * Возврат 1 = управление обработано (caller делает return). */
static uint8_t get_item_action(void)
{
    uint8_t a = pop_get_item_action();
    if (a == 1) { crouch(); return 1; }
    if (a == 2) {
        control_shift2 = CONTROL_IGNORE;       /* do_pickup: без авто-повтора */
        seqtbl_offset_char(SEQ_91_PICKUP_SWORD);
        return 1;
    }
    if (a == 3) {                              /* зелье — выпить (get_item) */
        control_shift2 = CONTROL_IGNORE;
        seqtbl_offset_char(SEQ_78_DRINK);
        return 1;
    }
    return 0;
}

static void run_jump(void)
{
    /* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
     * полировка K3; K2b просто запускает run-jump. */
    if (Char.frame >= FRAME_7_RUN) {
        control_up = release_arrows();
        seqtbl_offset_char(SEQ_4_RUN_JUMP);
    }
}

static void safe_step(void);   /* fwd */
static void draw_sword(void);  /* fwd: нужен в control_standing */

static void forward_pressed(void)
{
    int distance = pop_edge_distance();
    if (pop_edge_type() == EDGE_WALL && distance < 8) {
        /* рядом стена — вместо бега осторожный шаг (в стену не разбежится) */
        if (control_forward == CONTROL_HELD)
            safe_step();
    } else {
        seqtbl_offset_char(SEQ_1_START_RUN);
    }
}

static void back_pressed(void)
{
    control_backward = release_arrows();
    /* Kid без меча -> просто разворот. */
    seqtbl_offset_char(SEQ_5_TURN);
}

static void down_pressed(void)
{
    control_down = CONTROL_IGNORE;         /* без авто-повтора */
    /* У края пола (край сзади) со зацепом снизу — спуск на этаж (seq_68);
     * у передней кромки — отступ; иначе присед.  Решает pop_down_action. */
    if (!pop_down_action())
        crouch();
}

/* Прыжок вверх в стойке (seg005 check_jump_up): чистый вертикальный прыжок
 * ЛИБО прыжок-с-зацепом за уступ выше (запрыгнуть на этаж вверх).  Выбор seq
 * + выравнивание Kid.x — в pop_jump_up_seq (pop_map). */
static void jump_up(void)
{
    control_up = release_arrows();
    seqtbl_offset_char(pop_jump_up_seq());
}

static void up_pressed(void)
{
    /* Дверь уровня рядом и открыта → войти в неё: постановку в проём
     * (go_up_leveldoor) делает pop_leveldoor_enter, здесь остаётся только
     * последовательность подъёма по лестнице.  Телепортов у нас нет — это
     * модификация SDLPoP, а не оригинал.
     * Иначе: ↑+forward = прыжок с места, иначе вертикальный прыжок вверх. */
    if (pop_leveldoor_enter()) {
        seqtbl_offset_char(SEQ_GO_UP_LEVELDOOR);
        return;
    }
    if (control_x == CONTROL_HELD_FORWARD)
        standing_jump();
    else
        jump_up();
}

/* Осторожный шаг (Shift+forward): шаг РОВНО до кромки/стены.  distance>0 —
 * шаг длины distance (seq 29..42 = step1..step14); ==0 у края — шаг на
 * кромку (seq_44) либо неосторожный шаг за край (seq_39).  Порт seg005. */
static void safe_step(void)
{
    int distance;
    control_shift2 = CONTROL_IGNORE;       /* без авто-повтора */
    control_forward = CONTROL_IGNORE;
    distance = pop_edge_distance();
    if (distance) {
        Char.repeat = 1;
        seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
    } else if (pop_edge_type() == EDGE_WALL) {
        /* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
         * (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
        Char.repeat = 1;
        seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
    } else if (Char.repeat != 0) {
        Char.repeat = 0;
        seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
    } else {
        seqtbl_offset_char(SEQ_39_UNSAFE_STEP);   /* реальный обрыв (edge=CLOSER) */
    }
}

/* ---- control_* обработчики состояний ------------------------------ */
static void control_crouched(void)
{
    /* Shift в приседе — поднять предмет (seg005 control_crouched). */
    if (control_shift2 == CONTROL_HELD && get_item_action()) return;
    if (control_y != CONTROL_HELD_DOWN) {
        seqtbl_offset_char(SEQ_49_STAND_UP_FROM_CROUCH);
    } else if (control_forward == CONTROL_HELD) {
        control_forward = CONTROL_IGNORE;
        seqtbl_offset_char(SEQ_79_CROUCH_HOP);
    }
}

static void control_standing(void)
{
    /* Shift в стойке над предметом — присесть к нему (seg005:0358, первая
     * строка control_standing).  Идёт ДО остальных shift-веток. */
    if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
        get_item_action())
        return;
    /* Ветку «charid != kid && вниз+вперёд -> draw_sword» не портируем: у нас
     * соперник до control_standing не доходит, его перехватывает
     * control_guard_inactive в самом диспетчере (порядок как в seg005). */

    /* seg005:352 — Кид с мечом ВИДИТ активного стража: достаёт клинок сам.
     * Без этого игрок стоит с мечом в ножнах, пока его рубят. */
    if (pop_have_sword) {
        if (offguard != 0 && control_shift >= CONTROL_RELEASED) {
            /* Меч убран намеренно (down в бою) — не доставать, пока не
             * отпустят Shift; в оригинале это переход на loc_6213. */
        } else if (can_guard_see_kid >= 2) {
            int16_t d = pop_char_opp_dist();
            if (d >= -10 && d < 90) {
                holding_sword = 1;
                /* (word)d < (word)-6 — беззнаковое: истинно для ВСЕГО, кроме
                 * −6..−1.  То есть «страж вплотную за спиной» -> отступить,
                 * иначе достать меч. */
                if (d >= -6 && d < 0) { back_pressed(); return; }
                draw_sword();
                return;
            }
        } else {
            offguard = 0;
        }
    }

    if (control_shift == CONTROL_HELD) {
        if (control_backward == CONTROL_HELD)      back_pressed();
        else if (control_up == CONTROL_HELD)       up_pressed();
        else if (control_down == CONTROL_HELD)     down_pressed();
        else if (control_x == CONTROL_HELD_FORWARD && control_forward == CONTROL_HELD)
            safe_step();
    } else if (control_forward == CONTROL_HELD) {
        if (control_up == CONTROL_HELD) standing_jump();   /* keyboard mode */
        else forward_pressed();
    } else if (control_backward == CONTROL_HELD) {
        back_pressed();
    } else if (control_up == CONTROL_HELD) {
        if (control_forward == CONTROL_HELD) standing_jump();
        else up_pressed();
    } else if (control_down == CONTROL_HELD) {
        down_pressed();
    } else if (control_x == CONTROL_HELD_FORWARD) {
        forward_pressed();
    }
}

static void control_turning(void)
{
    if (control_shift >= CONTROL_RELEASED &&
        control_x == CONTROL_HELD_FORWARD && control_y >= CONTROL_RELEASED) {
        seqtbl_offset_char(SEQ_43_START_RUN_AFTER_TURN);
    }
}

static void control_startrun(void)
{
    if (control_y == CONTROL_HELD_UP && control_x == CONTROL_HELD_FORWARD)
        standing_jump();
}

static void control_running(void)
{
    /* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
     * seq_47 при беге в стену (как оригинал), с корректной позицией. */
    if (control_x == CONTROL_RELEASED &&
        (Char.frame == FRAME_7_RUN || Char.frame == FRAME_11_RUN)) {
        control_forward = release_arrows();
        seqtbl_offset_char(SEQ_13_STOP_RUN);
    } else if (control_x == CONTROL_HELD_BACKWARD) {
        control_backward = release_arrows();
        seqtbl_offset_char(SEQ_6_RUN_TURN);
    } else if (control_y == CONTROL_HELD_UP && control_up == CONTROL_HELD) {
        run_jump();
    } else if (control_down == CONTROL_HELD) {
        control_down = CONTROL_IGNORE;
        seqtbl_offset_char(SEQ_26_CROUCH_WHILE_RUNNING);
    }
}

/* ---- Вис (seg005 control_hanging/can_climb_up/hang_fall) ---------- *
 * Тайловые решения (можно ли подтянуться, куда сорваться) — в pop_map;
 * здесь управление осями control_*. */
static void hang_fall(void)
{
    control_down = release_arrows();
    seqtbl_offset_char(pop_hang_fall_seq());   /* seq_23/seq_11 (+ выравнивание x) */
}

static void can_climb_up(void)
{
    control_up = control_shift2 = release_arrows();
    seqtbl_offset_char(pop_hang_climb_up_seq());   /* seq_10 / seq_73 */
}

static void control_hanging(void)
{
    if (control_y == CONTROL_HELD && pop_hang_grab_ready()) {
        can_climb_up();                        /* ↑ — подтянуться             */
    } else if (control_shift == CONTROL_HELD) {
        int8_t w = pop_hang_wall();
        if (w > 0)      seqtbl_offset_char(SEQ_25_HANG_AGAINST_WALL);  /* у стены */
        else if (w < 0) hang_fall();                                   /* сорваться */
        /* w==0: сверху пол — держимся (ничего) */
    } else {
        hang_fall();                           /* отпустил Shift — сорваться   */
    }
}

/* control_jumpup (seg005): в старте прыжка вверх ←/→ или forward → перевести
 * в прыжок с места. */
static void control_jumpup(void)
{
    if (control_x == CONTROL_HELD_FORWARD || control_forward == CONTROL_HELD)
        standing_jump();
}

/* draw_sword (seg005:945): достать меч.  Кид переходит в seq_55 (подъём
 * клинка со звуком), соперник — сразу в стойку en garde (seq_90).  Стрелки
 * гасятся, чтобы «вперёд», которым меч достали, не сработало второй раз
 * уже как выпад. */
static void draw_sword(void)
{
    uint8_t seq_id = SEQ_55_DRAW_SWORD;
    control_forward = control_shift2 = release_arrows();
    if (Char.charid == CHARID_0_KID) {
        offguard = 0;              /* TODO: play_sound(19) — звука пока нет */
    } else if (Char.charid != CHARID_1_SHADOW) {
        seq_id = SEQ_90_EN_GARDE;  /* соперник встаёт сразу в стойку */
    }
    Char.sword = SWORD_2_DRAWN;
    seqtbl_offset_char(seq_id);
}

/* control_guard_inactive (seg006:2123): неактивный страж реагирует ТОЛЬКО в
 * кадре 166 (стойка) и только на «вниз»: вниз+вперёд — достать меч,
 * вниз без «вперёд» — развернуться (stand flipped). */
static void control_guard_inactive(void)
{
    if (Char.frame == FRAME_166_STAND_INACTIVE && control_down == CONTROL_HELD) {
        if (control_forward == CONTROL_HELD) {
            draw_sword();
        } else {
            control_down = CONTROL_IGNORE;
            seqtbl_offset_char(SEQ_80_STAND_FLIPPED);
        }
    }
}

/* ---- Боёвка (seg005:964..) ---------------------------------------- *
 * Всё это ветки ОБЩЕГО диспетчера: у стража тот же control(), просто
 * control_* ему выставляет ИИ (autocontrol_*, guards.c), а не клавиатура. */

/* back_with_sword (seg005): шаг назад — только из стоек. */
static void back_with_sword(void)
{
    uint8_t f = Char.frame;
    if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
        f == FRAME_171_STAND_WITH_SWORD) {
        control_backward = CONTROL_IGNORE;      /* без авто-повтора */
        seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD);
    }
}

/* forward_with_sword (seg005): шаг вперёд; у стража своя последовательность. */
static void forward_with_sword(void)
{
    uint8_t f = Char.frame;
    if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
        f == FRAME_171_STAND_WITH_SWORD) {
        control_forward = CONTROL_IGNORE;
        seqtbl_offset_char(Char.charid != CHARID_0_KID ? SEQ_56_GUARD_FORWARD
                                                       : SEQ_86_FORWARD_WITH_SWORD);
    }
}

/* sword_strike (seg005): выпад из стойки/шага либо добивание после парирования. */
static void sword_strike(void)
{
    uint8_t f = Char.frame;
    uint8_t seq_id;
    if (f == FRAME_157_WALK_WITH_SWORD || f == FRAME_158_STAND_WITH_SWORD ||
        f == FRAME_170_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD ||
        f == FRAME_165_WALK_WITH_SWORD) {
        seq_id = (Char.charid == CHARID_0_KID) ? SEQ_75_STRIKE : SEQ_58_GUARD_STRIKE;
    } else if (f == FRAME_150_PARRY || f == FRAME_161_PARRY) {
        seq_id = SEQ_66_STRIKE_AFTER_PARRY;
    } else {
        return;
    }
    control_shift2 = CONTROL_IGNORE;
    seqtbl_offset_char(seq_id);
}

/* parry (seg005): блок.  Условие сильно разное у Кида и у соперника —
 * соперник блокирует ТОЛЬКО на кадре 152 (второй кадр удара), а Кид ещё и
 * по 153 (и тогда последовательность прокручивается сразу). */
static void parry(void)
{
    uint8_t cf = Char.frame, of = Opp.frame;
    uint8_t seq_id = SEQ_62_PARRY;
    uint8_t do_play_seq = 0;

    if (cf == FRAME_158_STAND_WITH_SWORD || cf == FRAME_170_STAND_WITH_SWORD ||
        cf == FRAME_171_STAND_WITH_SWORD || cf == FRAME_168_BACK ||
        cf == FRAME_165_WALK_WITH_SWORD) {
        if (pop_char_opp_dist() >= 32 && Char.charid != CHARID_0_KID) {
            back_with_sword();
            return;
        } else if (Char.charid == CHARID_0_KID) {
            if (of == FRAME_168_BACK) return;
            if (of != FRAME_151_STRIKE_1 && of != FRAME_152_STRIKE_2 &&
                of != FRAME_162_BLOCK_TO_STRIKE) {
                if (of == FRAME_153_STRIKE_3) do_play_seq = 1;
            }
        } else {
            if (of != FRAME_152_STRIKE_2) return;
        }
    } else {
        if (cf != FRAME_167_BLOCKED) return;
        seq_id = SEQ_61_PARRY_AFTER_STRIKE;
    }
    control_up = CONTROL_IGNORE;
    seqtbl_offset_char(seq_id);
    if (do_play_seq) play_seq();
}

/* swordfight (seg005:0CDB) — разбор ввода в бою.  Порядок веток важен:
 * удар старше «убрать меч», тот старше блока, блок старше шагов. */
static void swordfight(void)
{
    uint8_t f = Char.frame;
    uint8_t seq_id;

    if (f == FRAME_161_PARRY && control_shift2 >= CONTROL_RELEASED) {
        seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD);   /* отскок после парирования */
        return;
    } else if (control_shift2 == CONTROL_HELD) {
        if (Char.charid == CHARID_0_KID) kid_sword_strike = 15;
        sword_strike();
        if (control_shift2 == CONTROL_IGNORE) return;
    }

    if (control_down == CONTROL_HELD) {
        if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
            f == FRAME_171_STAND_WITH_SWORD) {
            control_down = CONTROL_IGNORE;
            Char.sword = SWORD_0_SHEATHED;
            if (Char.charid == CHARID_0_KID) {
                offguard = 1;        /* убрал намеренно — сразу не доставать */
                guard_refrac = 9;    /* и страж на это время придержан */
                holding_sword = 0;
                seq_id = SEQ_93_PUT_SWORD_AWAY_FAST;
            } else if (Char.charid == CHARID_1_SHADOW) {
                seq_id = SEQ_92_PUT_SWORD_AWAY;
            } else {
                seq_id = SEQ_87_GUARD_BECOME_INACT;   /* Кид ушёл из виду */
            }
            seqtbl_offset_char(seq_id);
        }
    } else if (control_up == CONTROL_HELD) {
        parry();
    } else if (control_forward == CONTROL_HELD) {
        forward_with_sword();
    } else if (control_backward == CONTROL_HELD) {
        back_with_sword();
    }
}

/* control_with_sword (seg005:964): пока соперник виден и близко — бой;
 * иначе страж убирает меч, а Кид просто стоит. */
static void control_with_sword(void)
{
    if (Char.action >= 2) return;              /* actions_2_hang_climb и выше */
    if (pop_tile_at(Char.curr_col, Char.curr_row) == 11 /* loose */ ||
        can_guard_see_kid >= 2) {
        int16_t d = pop_char_opp_dist();
        if (d < 90) {
            if (d >= 0) { swordfight(); return; }
            /* Соперник за спиной: далеко — развернуться, вплотную — драться. */
            if (d < -4) { seqtbl_offset_char(SEQ_60_TURN_WITH_SWORD); return; }
            swordfight();
            return;
        }
    }
    /* Не видит / далеко. */
    if (Char.charid < CHARID_2_GUARD) {
        if (Char.frame == FRAME_171_STAND_WITH_SWORD) {
            Char.sword = SWORD_0_SHEATHED;
            seqtbl_offset_char(SEQ_92_PUT_SWORD_AWAY);   /* страж убит */
        }
    } else {
        swordfight();
    }
}

/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
void pop_control(void)
{
    uint8_t f = Char.frame;
    /* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
     * Из любой стойки он валится в seq_71_dying; остальное управление в
     * этом состоянии не обрабатывается.  Через это же место проходит чит K
     * (pop_guard_kill ставит Guard.alive = 0). */
    if (Char.alive >= 0) {
        if (f == FRAME_15_STAND || f == FRAME_166_STAND_INACTIVE ||
            f == FRAME_158_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD)
            seqtbl_offset_char(SEQ_71_DYING);
        return;
    }
    /* seg005 control(): в отскоке (bumped) и в свободном падении управление
     * НЕ обрабатывается вовсе — только гасятся стрелки.  Без этого игрок
     * прерывает присед после удара (medland/плита по голове) нажатием ↑, и
     * последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
     * стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
     * пару пикселей меньше, чем должен»). */
    if (Char.action == 5 || Char.action == 4) {   /* bumped / in_freefall */
        release_arrows();
        return;
    }
    /* Порядок веток — как в seg005: меч старше charid, charid старше кадров.
     * Пока Kid безоружен, обе ветки — про стража. */
    if (Char.sword == SWORD_2_DRAWN)
        control_with_sword();
    else if (Char.charid >= CHARID_2_GUARD)
        control_guard_inactive();
    else if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
        control_standing();
    else if (f == FRAME_48_TURN)
        control_turning();
    else if (f < 4)
        control_startrun();
    else if (f >= FRAME_67_JUMPUP1 && f < FRAME_70_JUMPHANG)
        control_jumpup();
    else if (f < FRAME_15_STAND)
        control_running();
    else if (f >= FRAME_87_HANG1 && f < 100)
        control_hanging();
    else if (f == FRAME_109_CROUCH)
        control_crouched();
}

/* ---- Модель ввода (seg006) ---------------------------------------- */
static void read_input(void)
{
    uint8_t l = kbd_raw_down(KBD_LEFT);
    uint8_t r = kbd_raw_down(KBD_RIGHT);
    uint8_t u = kbd_raw_down(KBD_UP);
    uint8_t d = kbd_raw_down(KBD_DOWN);
    /* Отладочный осторожный шаг: J/L = Shift+влево/вправо (см. KBD_DBG_STEP*). */
    uint8_t sl = kbd_raw_down(KBD_DBG_STEPL);
    uint8_t sr = kbd_raw_down(KBD_DBG_STEPR);
    if (sl) l = 1;
    if (sr) r = 1;
    control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED);
    control_y = d ? CONTROL_HELD_DOWN  : (u ? CONTROL_HELD_UP   : CONTROL_RELEASED);
    control_shift = (sl || sr || kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
                    ? CONTROL_HELD : CONTROL_RELEASED;
}

/* read_user_control (seg006:1557): «свежее нажатие» (RELEASED->HELD) с
 * подавлением авто-повтора (IGNORE держится, пока клавишу не отпустят). */
static void read_user_control(void)
{
    if (control_forward >= CONTROL_RELEASED) {
        if (control_x == CONTROL_HELD_FORWARD) {
            if (control_forward == CONTROL_RELEASED) control_forward = CONTROL_HELD;
        } else control_forward = CONTROL_RELEASED;
    }
    if (control_backward >= CONTROL_RELEASED) {
        if (control_x == CONTROL_HELD_BACKWARD) {
            if (control_backward == CONTROL_RELEASED) control_backward = CONTROL_HELD;
        } else control_backward = CONTROL_RELEASED;
    }
    if (control_up >= CONTROL_RELEASED) {
        if (control_y == CONTROL_HELD_UP) {
            if (control_up == CONTROL_RELEASED) control_up = CONTROL_HELD;
        } else control_up = CONTROL_RELEASED;
    }
    if (control_down >= CONTROL_RELEASED) {
        if (control_y == CONTROL_HELD_DOWN) {
            if (control_down == CONTROL_RELEASED) control_down = CONTROL_HELD;
        } else control_down = CONTROL_RELEASED;
    }
    if (control_shift2 >= CONTROL_RELEASED) {
        if (control_shift == CONTROL_HELD) {
            if (control_shift2 == CONTROL_RELEASED) control_shift2 = CONTROL_HELD;
        } else control_shift2 = CONTROL_RELEASED;
    }
}

static void flip_control_x(void)
{
    int8_t t;
    control_x = (int8_t)-control_x;
    t = control_forward; control_forward = control_backward; control_backward = t;
}

/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
static void user_control(void)
{
    if (Char.direction >= DIR_RIGHT) {
        flip_control_x();
        pop_control();
        flip_control_x();
    } else {
        pop_control();
    }
}

static void rest_ctrl_1(void)
{
    control_forward = ctrl1_forward; control_backward = ctrl1_backward;
    control_up = ctrl1_up; control_down = ctrl1_down; control_shift2 = ctrl1_shift2;
}
static void save_ctrl_1(void)
{
    ctrl1_forward = control_forward; ctrl1_backward = control_backward;
    ctrl1_up = control_up; ctrl1_down = control_down; ctrl1_shift2 = control_shift2;
}

/* ---- Публичный API ------------------------------------------------ */
int pop_ctrl_init(void)
{
    control_forward = control_backward = control_up = control_down = CONTROL_RELEASED;
    control_shift2 = CONTROL_RELEASED;
    ctrl1_forward = ctrl1_backward = ctrl1_up = ctrl1_down = ctrl1_shift2 = CONTROL_RELEASED;
    return kbd_raw_open();
}

void pop_ctrl_close(void)
{
    kbd_raw_close();
}

int8_t pop_ctrl_shift_held(void)
{
    return (int8_t)(control_shift == CONTROL_HELD);
}

void pop_ctrl_tick(void)
{
    kbd_raw_sync();          /* снять залипшее после Rx-overrun SIO */
    /* control_kid (seg006:0CD1), ПЕРВАЯ строка: HP кончились — персонаж
     * мёртв.  Без этой ветки смерть от меча стража не доводилась до конца:
     * hurt_by_sword играл seq_85, hitp_curr падал в 0, но Kid.alive
     * оставался −1 и pop_kid_dead не взводился — Кид досматривал сиквенс,
     * возвращался в стойку с нулём HP, и страж убивал его снова по кругу
     * (BUG-DEATH-1).  pop_kid_dead — наш сигнал главному циклу на респавн
     * (в оригинале это счётчик Char.alive в play_kid, seg006:0BEE). */
    if (Kid.alive < 0 && hitp_curr == 0) {
        Kid.alive = 0;
        pop_kid_dead = 1;
    }
    rest_ctrl_1();
    read_input();
    read_user_control();
    /* Окно Char вокруг диспетчера: control() работает с АКТИВНЫМ
     * персонажем (как в оригинале), поэтому ввод игрока применяется к
     * Киду только внутри loadkid/savekid.  Кадр не перезагружаем —
     * play_seq в kid_tick сделает это следующим шагом. */
    pop_loadkid();
    user_control();      /* -> pop_control(): может сменить Char.curr_seq */
    pop_savekid_state();
    save_ctrl_1();
}
