applications/PoP: порт Prince of Persia — PoC (roomtest) + пайплайн

Порт PoP на Sprinter.  Текущий PoC — applications/PoP/roomtest/:
комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре
и коллизией с картой.

- roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип,
  seqtbl-анимация), pop_ctrl (порт control() PoP на held-state
  kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены,
  падение/приземление, отскок seq_47, вертикальный прыжок K4.1).
  MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge).
- toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract —
  распаковка res-графики MSDOS в атласы + композиция комнат.
- toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD),
  png_strip.py / room_compose.py (ассет-пайплайн).
- docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT).
- bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип).

.gitignore: build-артефакты applications/*/*/*; исключены внешние
референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и
оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 17:51:08 +03:00
parent 484b18d10c
commit cd8d566d82
196 changed files with 6630 additions and 0 deletions
+368
View File
@@ -0,0 +1,368 @@
/*
* 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"
/* ---- 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_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 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). */
static int8_t control_x, control_y, control_shift;
static 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). */
#define seqtbl_offset_char(seq) kid_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);
}
static void run_jump(void)
{
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
* полировка K3; K2b просто запускает run-jump. */
if (Kid.frame >= FRAME_7_RUN) {
control_up = release_arrows();
seqtbl_offset_char(SEQ_4_RUN_JUMP);
}
}
static void safe_step(void); /* fwd */
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; /* без авто-повтора */
/* climb-down у края (seq_68) — K4 (зацеп/спуск); пока просто присед. */
crouch();
}
/* Вертикальный прыжок вверх (seg005 jump_up, упрощённо): сверху сплошное
* (стена/пол) → seq_14 (в потолок), иначе seq_28 (пусто сверху). Зацеп за
* уступ сверху-впереди (can_grab) — K4.2, пока чистый вертикальный прыжок. */
static void jump_up(void)
{
control_up = release_arrows();
if (pop_above_solid())
seqtbl_offset_char(SEQ_14_JUMP_INTO_CEILING);
else
seqtbl_offset_char(SEQ_28_JUMP_NOTHING_ABOVE);
}
static void up_pressed(void)
{
/* (дверь уровня/телепорт — нет в тесте.) ↑+forward = прыжок с места;
* иначе вертикальный прыжок вверх. */
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) {
Kid.repeat = 1;
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
} else if (pop_edge_type() == EDGE_WALL) {
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
Kid.repeat = 1;
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
} else if (Kid.repeat != 0) {
Kid.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)
{
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)
{
/* Kid charid_0, без меча: остаётся нижняя часть control_standing. */
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 &&
(Kid.frame == FRAME_7_RUN || Kid.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);
}
}
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
static void control(void)
{
uint8_t f = Kid.frame;
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 — K4 */
else if (f < FRAME_15_STAND)
control_running();
else if (f >= FRAME_87_HANG1 && f < 100)
; /* control_hanging — K4 */
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);
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 = (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 (Kid.direction >= DIR_RIGHT) {
flip_control_x();
control();
flip_control_x();
} else {
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();
}
void pop_ctrl_tick(void)
{
rest_ctrl_1();
read_input();
read_user_control();
user_control(); /* -> control(): может сменить Kid.curr_seq */
save_ctrl_1();
}