cd8d566d82
Порт 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>
383 lines
15 KiB
C
383 lines
15 KiB
C
/*
|
||
* 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"
|
||
|
||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||
#define TILE_EMPTY 0
|
||
#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 */
|
||
|
||
#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 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) */
|
||
|
||
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);
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
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) */
|
||
} 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; }
|
||
|
||
/* Тайл НАД персонажем (curr_row-1, curr_col) сплошной (стена/пол)? — выбор
|
||
* seq прыжка вверх: solid -> seq_14 (в потолок), иначе seq_28 (пусто сверху).
|
||
* K4.1 упрощение: без grab-логики (уступ сверху-впереди) — та в K4.2. */
|
||
uint8_t pop_above_solid(void)
|
||
{
|
||
uint8_t t = get_tile(Kid.curr_col, (int8_t)(Kid.curr_row - 1));
|
||
return (t == TILE_WALL || tile_is_floor(t));
|
||
}
|
||
|
||
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
||
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
||
int pop_wall_ahead(void)
|
||
{
|
||
int d = pop_edge_distance();
|
||
return (g_edge_type == EDGE_WALL && d <= 2);
|
||
}
|
||
|
||
/* ---- Диспетчер ----------------------------------------------------- */
|
||
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;
|
||
if (Kid.action == ACT_FREEFALL || Kid.action == ACT_HANGCLIMB) 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)
|
||
{
|
||
fall_accel();
|
||
fall_speed();
|
||
determine_col();
|
||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||
check_action();
|
||
}
|