L3-CHKP, зацеп в прыжке опцией, pop_tune.h; сборка 10 мин -> 1:48

L3-CHKP (чекпойнт уровня 3).  Флаг взводится, когда Кид уходит ВЛЕВО ИЗ
комнаты 7, а do_startpos по нему подменяет старт на комнату 2, тайл (0,6),
лицом влево и снимает loose-плиту (7,0,4).  Тонкость, на которой я сначала
ошибся: level3_set_chkp (seg002:0665) вызван из leave_room ДО
goto_other_room, поэтому `Char.room == 7` — это комната, ИЗ которой уходят,
а не в которую входят.  Поймал пользователь прогоном в SDLPoP: смерть В
комнате 7 вернула его в стартовую 9, а плита осталась цела.  Проверено в
MAME: вход в 7 флаг не ставит, уход влево — ставит; респавн в комнате 2;
после обычной смерти (без чекпойнта) плиты восстанавливаются как раньше.

Зацеп ПРЯМО В ПРЫЖКЕ (check_grab_run_jump, seg006:1228) — портирован за
выключателем POP_ENABLE_JUMP_GRAB.  Это НЕ ваниль: в оригинале зацепиться
можно только в начале падения (кадры 102..105), то есть Shift приходится
жать уже в полёте; у SDLPoP это enable_jump_grab, и работает он лишь при
включённых fixes-and-enhancements.  Три точки вызова как у оригинала:
check_action и обе ветки check_bumped (зацеп за верх стены вместо удара).

pop_tune.h — настраиваемые константы в одном месте (аналог
custom_options_type SDLPoP): чекпойнт, выключатель зацепа и отладочная
крутилка POP_DBG_GATE_HOLD (сколько кадров решётка держится поднятой;
оригинал 5, потолок 30 — таймер связи пятибитный, 31 = «заклинено»).
Задача TUNE-1 в TASKS.md: читать это из ini рядом с exe.

СБОРКА.  --max-allocs-per-node снижен со 100000 (дефолт sprinter-cc) до
3000 (дефолт SDCC) через `make ALLOCS=...`, а pop_trob.c уехал в БАНК 6 —
на 3000 резидент иначе не влезает (замер: конец _HOME 0xBC69 при стеке с
0xBB00).  Итог: сборка с нуля 1:48 вместо >10 минут, куча 2751 Б вместо
2298.  Релизная сборка — make ALLOCS=100000; сравнивать занятость банков
можно только при одинаковом ALLOCS.

Отдельно (вне git, SDLPoP в .gitignore): из референса вычищена вся наша
отладка DBG-GRAB — трасса JMP, GRAB try/probe/fail/OK/skip, автоскриншоты
seg003, печати DBG mob/mid/overlay/kidobj и счётчик dbg_shots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-06 00:57:51 +03:00
parent c6cadd0140
commit 0cd6b2d737
8 changed files with 261 additions and 23 deletions
+82
View File
@@ -32,6 +32,7 @@
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
#include "pop_level.h" /* pop_level_start_room — вход в дверь уровня */
#include "pop_guard.h" /* SWORD_2_DRAWN — ветка приземления с мечом */
#include "pop_tune.h" /* POP_ENABLE_JUMP_GRAB и прочие настройки */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
#define TILE_EMPTY 0
@@ -69,6 +70,7 @@
#define SEQ_47_BUMP 47 /* удар в стену: dx(-4) отскок + 50-52 + stand */
/* K4 — зацеп/вис/подтягивание/спуск */
#define SEQ_8_GRAB_STRAIGHT 8 /* прыжок вверх и зацеп (прямо) */
#define SEQ_9_GRAB_WHILE_JUMPING 9 /* зацеп ПРЯМО В ПРЫЖКЕ (улучшение SDLPoP) */
#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */
#define SEQ_16_JUMP_UP_AND_GRAB 16 /* прыжок вверх и зацеп (нет пола сзади)*/
#define SEQ_24_GRAB_FORWARD 24 /* прыжок вверх и зацеп (пол сзади) */
@@ -628,6 +630,70 @@ static void check_grab(void)
}
}
static void make_loose_fall(int pos, uint8_t modifier);
/* check_grab_run_jump — ЗАЦЕП ПРЯМО В ПРЫЖКЕ, при Shift, зажатом ЗАРАНЕЕ.
*
* ВАЖНО: в оригинальной игре этого НЕТ. Это улучшение SDLPoP
* (`USE_JUMP_GRAB` + `fixes->enable_jump_grab`, seg006:1228), и там оно
* выключено, пока не включены fixes-and-enhancements. Ванильное поведение:
* зацепиться можно только в НАЧАЛЕ ПАДЕНИЯ (check_grab, кадры 102..105) —
* то есть Shift надо успеть нажать уже в полёте. Пользователь ловил ровно
* это и просил «чтобы работало и при заранее зажатом Shift», поэтому
* улучшение портировано, но за выключателем POP_ENABLE_JUMP_GRAB
* (pop_tune.h): игровая сборка сможет вернуться к оригиналу.
*
* Условия оригинала: действие — прыжок, кадр 22..23 (прыжок с места) или
* 39..41 (разбег-прыжок), «вперёд» ОТПУЩЕНО, «вверх» зажато. Дальше — та
* же проба, что у check_grab, но персонаж не падает, а сразу ставится на
* кромку и играет seq_9.
*
* Упрощение против SDLPoP: там колонка захвата правится, когда тайл
* оказался в ЧУЖОЙ комнате (curr_room != Char.room) — у нас модель
* однокомнатная, get_tile сам резолвит колонки за швом в координатах
* ТЕКУЩЕЙ комнаты, поэтому правка не нужна. Зато кнопка/плита за швом
* (grab_col вне 0..9) не сработают — тайл принадлежит соседу, а
* pop_trob_modif/g_fg у нас про свою комнату; такие случаи пропускаем. */
static uint8_t check_grab_run_jump(void)
{
uint8_t frame = Kid.frame;
uint8_t grab_tile;
int8_t grab_col, grab_row;
/* Сам выключатель проверяют ТОЧКИ ВЫЗОВА (иначе SDCC ругается на
* недостижимый код при POP_ENABLE_JUMP_GRAB = 1). */
if (Kid.action != ACT_RUNJUMP) return 0;
if (!((frame >= 22 && frame <= 23) || (frame >= 39 && frame <= 41))) return 0;
if (control_x != POP_CONTROL_RELEASED || control_y != POP_CONTROL_HELD_UP) return 0;
if (!can_grab_front_above()) return 0;
grab_tile = g_curr_tile; /* тайл, за который взялись */
grab_col = g_infrontx;
grab_row = (int8_t)(Kid.curr_row - 1); /* СНЯТЬ до play_seq: он двигает ряд */
Kid.x = (uint8_t)(pop_x_bump[grab_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX);
Kid.x = (uint8_t)char_dx_forward(Kid.direction < 0 ? -12 : 2);
Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
kid_set_seq(SEQ_9_GRAB_WHILE_JUMPING);
pop_kid_play();
determine_col();
grab_timer = 12;
/* Хвост оригинала: за что схватились — то и сработает. Нужен потому,
* что check_press на следующем кадре уже не поможет: кадр сменится на
* 81 (hangdrop), если Shift отпустят сразу. */
if (grab_col >= 0 && grab_col <= 9 && grab_row >= 0 && grab_row <= 2) {
uint8_t tp = (uint8_t)(grab_row * 10 + grab_col);
if (grab_tile == TILE_OPENER || grab_tile == TILE_CLOSER)
pop_trigger_button(g_room, tp, grab_tile, pop_trob_modif(g_room)[tp]);
else if (grab_tile == TILE_LOOSE) {
is_guard_notice = 1;
make_loose_fall(tp, 1);
}
}
return 1;
}
static void do_fall(void)
{
uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
@@ -1093,6 +1159,11 @@ static void check_on_floor(void)
static void check_action(void)
{
uint8_t action = Kid.action, frame = Kid.frame;
/* Зацеп прямо в прыжке (улучшение SDLPoP, seg006:0914) — ДО всего
* остального: если получилось, кадр уже сменён на вис. */
if (POP_ENABLE_JUMP_GRAB && action == ACT_RUNJUMP &&
pop_ctrl_shift_held() && check_grab_run_jump())
return;
if (action == ACT_BUMPED || action == ACT_HANGSTRAIGHT) {
/* ТОЛЬКО кадр 109 (присед) — как в ванильном seg006:909. Кадры
* вставания 110..119 сюда добавляет ОПЦИОНАЛЬНЫЙ FIX_STAND_ON_THIN_AIR,
@@ -1378,6 +1449,13 @@ static void check_bumped_look_right(void)
int coll_left;
if (Kid.direction != 0) return; /* (меча в руке у нас нет) */
if (!is_obstacle_at_col(col)) return;
/* Прежде чем удариться о стену — попытка зацепиться за её верх
* (seg004:0205, то же улучшение). Не вышло — тайл препятствия надо
* перечитать: проба зацепа сбила g_curr_tile. */
if (POP_ENABLE_JUMP_GRAB && pop_ctrl_shift_held()) {
if (check_grab_run_jump()) return;
if (!is_obstacle_at_col(col)) return;
}
wt = wall_type(g_curr_tile);
if (!wt) return;
coll_left = pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
@@ -1393,6 +1471,10 @@ static void check_bumped_look_left(void)
int coll_left;
if (Kid.direction >= 0) return;
if (!is_obstacle_at_col(col)) return;
if (POP_ENABLE_JUMP_GRAB && pop_ctrl_shift_held()) { /* seg004:0186 */
if (check_grab_run_jump()) return;
if (!is_obstacle_at_col(col)) return;
}
wt = wall_type(g_curr_tile);
if (!wt) return;
coll_left = pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;