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:
@@ -21,14 +21,28 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
||||
# горячих банк→банк переходов не создаёт (в отличие от pop_level, чей
|
||||
# pop_level_tile зовётся из банка 2 на КАЖДЫЙ тайл). Шина control_*
|
||||
# переехала в pop_state.c — см. там.
|
||||
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c $(PROF_FLAGS)
|
||||
# --max-allocs-per-node: во сколько вариантов размещения регистров SDCC
|
||||
# упирается на узел. У sprinter-cc дефолт 100000 (агрессивно, как fast-сборки
|
||||
# библиотек) — на roomtest это МИНУТЫ на банк, потому что модули крупные.
|
||||
# Дефолт SDCC — 3000, и для разработки его достаточно: разница в размере
|
||||
# единицы процента, а сборка кратно быстрее. Замер (2026-08-05) — в шапке
|
||||
# TASKS.md, запись BUILD-FAST.
|
||||
#
|
||||
# make — быстрая сборка (ALLOCS=3000)
|
||||
# make ALLOCS=100000 — как раньше: минимальный код, для замеров размера
|
||||
# и для «релизного» образа
|
||||
#
|
||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||
ALLOCS ?= 3000
|
||||
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||
# MAME = время фазы в растровых строках.
|
||||
PROF ?= 1
|
||||
PROF_FLAGS := -DPROF_BORDER=$(PROF)
|
||||
EXTRA_SRCS := pop_state.c pop_draw.c pop_kid.c pop_level.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
|
||||
EXTRA_SRCS := pop_state.c pop_draw.c pop_kid.c pop_level.c pop_redraw.c pop_geom.c pop_guard.c
|
||||
|
||||
BG_DIR := $(CURDIR)/../poc/res/bg
|
||||
KID_DIR := $(CURDIR)/../poc/res/kid
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
| 4 | [L3-CHKP](#l3-chkp) | чекпойнт ур. 3 | корректный респавн ур. 3 |
|
||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||
| — | [L1-PASS](#l1-pass) | сквозной прогон ур. 1 + таблица 24 комнат | приёмка ур. 1 |
|
||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
||||
| ✔ | [DBG-CHEATS](#dbg-cheats) | `[`/`]` — подгонка Кида по X | **СДЕЛАНО 2026-08-05** |
|
||||
| ✔ | [MEM-BANK5](#mem-bank5) | `pop_ctrl.c` → банк 5 | **СДЕЛАНО 2026-08-05**: куча 180 Б → 2298 Б |
|
||||
|
||||
@@ -197,6 +198,34 @@ loose-плиту (комната 7, колонка 4, ряд 0). Механик
|
||||
есть (сделана в L2), остаётся сам флаг и подмена старта.
|
||||
|
||||
|
||||
### <a id="tune-1"></a>TUNE-1. Параметры движка — в конфиг, а не в код
|
||||
|
||||
**Что уже есть.** `pop_tune.h` — все настраиваемые числа собраны в одном
|
||||
заголовке: чекпойнт уровня 3 (`POP_CHKP_*`), отладочное окно решётки
|
||||
(`POP_DBG_GATE_HOLD`), включатель зацепа в прыжке (`POP_ENABLE_JUMP_GRAB`).
|
||||
Правило уже действует: новое «особое событие» или тайминг заводится ТАМ, а
|
||||
не константой по месту.
|
||||
|
||||
**Что нужно сделать.** Читать их из ФАЙЛА рядом с exe, чтобы менять без
|
||||
пересборки — под отладку («растянуть решётку, чтобы успеть пройти») и под
|
||||
моды. Формат: простой ini/`ключ=значение`, парсер на ~50 строк (числа,
|
||||
комментарии `;`, неизвестные ключи игнорировать), файл необязателен —
|
||||
нет файла, значит зашитые дефолты. Секции по смыслу: `[level]`,
|
||||
`[debug]`, `[enhancements]`.
|
||||
|
||||
**Ориентир — SDLPoP.** У него это `custom_options_type` (types.h) +
|
||||
`SDLPoP.ini` + меню Settings/Mods; наши имена намеренно совпадают с его
|
||||
(`custom->имя`), чтобы сверка оставалась механической. Осмотр его меню и
|
||||
опций — часть задачи: у него уже разложены по группам стартовые
|
||||
HP/минуты, номера «особых» комнат и уровней (шадоу, скелет, зеркало,
|
||||
чекпойнт), тайминги ворот и пик, скорости, а отдельной группой —
|
||||
`fixes`/`enhancements` (включая `enable_jump_grab`, который мы уже
|
||||
портировали). Брать всё подряд не надо: переносим по мере того, как
|
||||
константа реально понадобилась в игре.
|
||||
|
||||
**Оговорка по памяти.** Парсер и таблица параметров — холодный код,
|
||||
исполняется один раз при старте: кандидат в банк, а не в резидент W1.
|
||||
|
||||
### KBD-1. Shift + стрелки: нажатия теряются — **ПРИЧИНА НАЙДЕНА, ФИКС ПРОВЕРЕН**
|
||||
|
||||
> **ПОПРАВКА К ПОСЫЛКЕ (2026-08-05).** Ниже «fake shift» подан как
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* нельзя звать из __banked-кода. */
|
||||
#include "pop_bg.h"
|
||||
#include "pop_guard.h" /* is_guard_notice — нажатая кнопка слышна стражу */
|
||||
#include "pop_tune.h" /* POP_DBG_GATE_HOLD — отладочное растягивание окна */
|
||||
|
||||
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
||||
#define TILE_SPIKE 0x02
|
||||
@@ -54,7 +55,7 @@ static pop_rnd_t trob_seed;
|
||||
#define trob_prandom(maxv) pop_prandom(&trob_seed, (maxv))
|
||||
|
||||
|
||||
void pop_trob_reset(void)
|
||||
void pop_trob_reset(void) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
|
||||
@@ -63,7 +64,7 @@ void pop_trob_reset(void)
|
||||
trobs_count = 0;
|
||||
}
|
||||
|
||||
uint8_t *pop_trob_modif(uint8_t room)
|
||||
uint8_t *pop_trob_modif(uint8_t room) __banked
|
||||
{
|
||||
if (room < 1 || room > POP_NROOMS) return 0;
|
||||
if (!room_seen[room - 1]) {
|
||||
@@ -108,7 +109,7 @@ static int find_trob(uint8_t room, uint8_t tilepos)
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked
|
||||
{
|
||||
int f = find_trob(room, tilepos);
|
||||
if (f >= 0) { trobs[f].type = type; return; }
|
||||
@@ -119,7 +120,7 @@ void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
||||
trobs_count++;
|
||||
}
|
||||
|
||||
void pop_trob_room_changed(void)
|
||||
void pop_trob_room_changed(void) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
|
||||
@@ -130,7 +131,7 @@ void pop_trob_room_changed(void)
|
||||
* тайлов: факелам — случайная стартовая фаза пламени (start_anim_torch,
|
||||
* seg007:0847: modif = prandom(8)), зельям — случайная фаза пузырька
|
||||
* (start_anim_potion: младшие 3 бита = prandom(6)+1, старшие = тип). */
|
||||
void pop_trob_anim_room(uint8_t room)
|
||||
void pop_trob_anim_room(uint8_t room) __banked
|
||||
{
|
||||
uint8_t *mod = pop_trob_modif(room);
|
||||
uint8_t i, code;
|
||||
@@ -280,11 +281,14 @@ static void do_trigger_list(uint8_t index, uint8_t button_type)
|
||||
}
|
||||
|
||||
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||
uint8_t button_type, uint8_t modifier)
|
||||
uint8_t button_type, uint8_t modifier) __banked
|
||||
{
|
||||
uint8_t link_timer = get_doorlink_timer(modifier);
|
||||
if (link_timer != 0x1F) { /* не заклинено */
|
||||
set_doorlink_timer(modifier, 5);
|
||||
/* Оригинал ставит 5; вынесено в pop_tune.h, потому что проверять
|
||||
* руками проход в закрывающуюся решётку при таком окне почти
|
||||
* невозможно (см. BUG-GATE-PASS-1). */
|
||||
set_doorlink_timer(modifier, POP_DBG_GATE_HOLD);
|
||||
if (link_timer < 2) {
|
||||
pop_add_trob(room, tilepos, 1); /* кнопка нажимается */
|
||||
/* seg007:766: щелчок кнопки — шум, страж оборачивается. Звука у
|
||||
@@ -362,7 +366,7 @@ static void animate_door(uint8_t *m, int8_t *type)
|
||||
*m = mod;
|
||||
}
|
||||
|
||||
uint8_t pop_trob_room_active(uint8_t room)
|
||||
uint8_t pop_trob_room_active(uint8_t room) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < trobs_count; i++)
|
||||
@@ -387,7 +391,7 @@ static uint8_t torch_tick;
|
||||
#define TORCH_ANIM_NOW 1
|
||||
#endif
|
||||
|
||||
void pop_process_trobs(uint8_t cur_room)
|
||||
void pop_process_trobs(uint8_t cur_room) __banked
|
||||
{
|
||||
uint8_t i, w = 0;
|
||||
#if TORCH_ANIM_DIV > 1
|
||||
|
||||
@@ -25,41 +25,41 @@
|
||||
|
||||
/* Сброс всего состояния (новый уровень / рестарт): забыть все комнаты и
|
||||
* очистить список trob. */
|
||||
void pop_trob_reset(void);
|
||||
void pop_trob_reset(void) __banked;
|
||||
|
||||
/* Указатель на persistent modifier-массив комнаты room (1..24), 30 байт.
|
||||
* При ПЕРВОМ обращении лениво инициализируется из backtable уровня
|
||||
* (pop_level_room_bg). Далее ЖИВЁТ (не перезагружается). room вне
|
||||
* диапазона → 0. */
|
||||
uint8_t *pop_trob_modif(uint8_t room);
|
||||
uint8_t *pop_trob_modif(uint8_t room) __banked;
|
||||
|
||||
/* Добавить/обновить trob (room, tilepos, type). Если уже есть — сменить
|
||||
* type (как SDLPoP add_trob/find_trob). type<0 = будет удалён. */
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type);
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked;
|
||||
|
||||
/* Продвинуть все trob на кадр (animate_* по коду тайла из уровня), записать
|
||||
* modif, перерисовать тайлы ТЕКУЩЕЙ (cur_room) комнаты на back-странице.
|
||||
* Звать в основном цикле ПОСЛЕ set_draw_page(back), рядом с loose. */
|
||||
void pop_process_trobs(uint8_t cur_room);
|
||||
void pop_process_trobs(uint8_t cur_room) __banked;
|
||||
|
||||
/* Кид встал на кнопку (opener/closer): взвести таймер связи + обойти цепочку
|
||||
* LINKLOC, добавив trob целям (ворота). Порт seg007 trigger_button.
|
||||
* room/tilepos — кнопка; button_type — её тайл-код; modifier — её modif
|
||||
* (= индекс в LINKLOC). Звать из check_press (pop_map). */
|
||||
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||
uint8_t button_type, uint8_t modifier);
|
||||
uint8_t button_type, uint8_t modifier) __banked;
|
||||
|
||||
/* Есть ли активные trob в комнате room? Для перерисовки ворот соседа через
|
||||
* шов (roomtest не дёргает шов, пока сосед не анимируется). */
|
||||
uint8_t pop_trob_room_active(uint8_t room);
|
||||
uint8_t pop_trob_room_active(uint8_t room) __banked;
|
||||
|
||||
/* Смена комнаты: сбросить отложенные перерисовки «покоя» текущей комнаты
|
||||
* (trob-состояние в room_modif персистентно и НЕ трогается). Звать в
|
||||
* enter_room. */
|
||||
void pop_trob_room_changed(void);
|
||||
void pop_trob_room_changed(void) __banked;
|
||||
|
||||
/* anim_tile_modif (seg000:0B12): завести анимации тайлов комнаты (факелы,
|
||||
* зелья) со случайной стартовой фазой. Звать в enter_room. */
|
||||
void pop_trob_anim_room(uint8_t room);
|
||||
void pop_trob_anim_room(uint8_t room) __banked;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* pop_tune.h — НАСТРАИВАЕМЫЕ КОНСТАНТЫ движка (аналог custom_options
|
||||
* SDLPoP) в одном месте.
|
||||
*
|
||||
* Зачем отдельный заголовок. У оригинала «особые события» уровней зашиты
|
||||
* магическими числами прямо в код (`current_level == 3 && Char.room == 7`),
|
||||
* и SDLPoP вынес их в структуру `custom_options_type` (types.h) с
|
||||
* загрузкой из SDLPoP.ini и правкой через меню Settings/Mods. Нам нужно
|
||||
* то же самое по двум причинам:
|
||||
*
|
||||
* 1. ОТЛАДКА. Некоторые сцены руками почти не проверить (успеть в
|
||||
* закрывающуюся решётку), и единственный вменяемый способ — временно
|
||||
* растянуть тайминг. Такие крутилки обязаны жить в одном месте, а не
|
||||
* прятаться числами по трём модулям.
|
||||
* 2. МОДЫ. Тот, кто захочет свой набор уровней, меняет ровно эти числа.
|
||||
*
|
||||
* Пока это только `#define` — ни INI, ни меню нет (см. TASKS.md, запись
|
||||
* TUNE-1). Но правило уже действует: НОВОЕ «особое событие» или тайминг
|
||||
* заводится ЗДЕСЬ, а не константой по месту. Имена — как у SDLPoP
|
||||
* (custom->имя), чтобы сверка с оригиналом оставалась механической.
|
||||
*/
|
||||
#ifndef POP_TUNE_H
|
||||
#define POP_TUNE_H
|
||||
|
||||
/* ---- Чекпойнт уровня 3 (SDLPoP: checkpoint_*) ----------------------- *
|
||||
* Оригинал: level3_set_chkp (seg002:0665) взводит флаг, когда Кид уходит
|
||||
* ВЛЕВО ИЗ комнаты 7 (вызов стоит до goto_other_room, поэтому Char.room там
|
||||
* ещё старая); do_startpos (seg003:0141) подменяет по
|
||||
* нему стартовую позицию и убирает loose-плиту, через которую туда
|
||||
* попадают (иначе после респавна дорога назад была бы уже провалена). */
|
||||
#define POP_CHKP_LEVEL 3 /* checkpoint_level */
|
||||
#define POP_CHKP_ROOM 7 /* комната, ИЗ КОТОРОЙ уходят влево (у
|
||||
* SDLPoP хардкод; это НЕ комната
|
||||
* назначения — см. roomtest.c) */
|
||||
#define POP_CHKP_RESPAWN_ROOM 2 /* checkpoint_respawn_room */
|
||||
#define POP_CHKP_RESPAWN_TILEPOS 6 /* checkpoint_respawn_tilepos */
|
||||
#define POP_CHKP_RESPAWN_DIR (-1) /* checkpoint_respawn_dir: влево */
|
||||
#define POP_CHKP_CLEAR_ROOM 7 /* checkpoint_clear_tile_room */
|
||||
#define POP_CHKP_CLEAR_COL 4 /* checkpoint_clear_tile_col */
|
||||
#define POP_CHKP_CLEAR_ROW 0 /* checkpoint_clear_tile_row */
|
||||
|
||||
/* ---- Отладочные крутилки (в оригинале и в SDLPoP их НЕТ) ------------ *
|
||||
* POP_DBG_GATE_HOLD — сколько кадров решётка держится поднятой после
|
||||
* нажатия кнопки, ПРЕЖДЕ чем пойдёт вниз. В оригинале это таймер связи
|
||||
* дверей: trigger_button ставит 5, animate_button вычитает по единице, и
|
||||
* решётка стоит, пока таймер > 1 (то есть ~4 кадра после того, как Кид
|
||||
* сошёл с кнопки). Проверять руками проход в закрывающуюся решётку при
|
||||
* таком окне почти невозможно, поэтому значение вынесено сюда: поставь
|
||||
* 30 — и успеешь дойти спокойно. ОБЯЗАН быть 5 в игровой сборке.
|
||||
*
|
||||
* ПОТОЛОК 30. Таймер связи — пять бит модификатора кнопки (маска 0x1F), а
|
||||
* значение 31 у оригинала означает «событие заклинено» (`link_timer != 0x1F`
|
||||
* в trigger_button). Больше 30 ставить нельзя — кнопка перестанет
|
||||
* срабатывать вовсе. */
|
||||
#define POP_DBG_GATE_HOLD 5
|
||||
|
||||
/* ---- Улучшения SDLPoP (в ОРИГИНАЛЕ их нет) -------------------------- *
|
||||
* POP_ENABLE_JUMP_GRAB — зацепиться за кромку можно ПРЯМО В ПРЫЖКЕ, если
|
||||
* Shift зажат заранее (SDLPoP `enable_jump_grab`, seg006:1228). Ваниль
|
||||
* разрешает зацеп только в начале падения (`check_grab`, кадры 102..105),
|
||||
* то есть Shift приходится нажимать уже в полёте — это и раздражало на
|
||||
* приёмке. 0 — как в оригинале, 1 — как в SDLPoP с включёнными
|
||||
* enhancements. */
|
||||
#define POP_ENABLE_JUMP_GRAB 1
|
||||
|
||||
#endif /* POP_TUNE_H */
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "pop_geom.h" /* pop_y_to_row, TILE_SIZEX — тайл объекта */
|
||||
#include "pop_guard.h"
|
||||
#include "pop_cheat.h"
|
||||
#include "pop_tune.h"
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
|
||||
@@ -60,7 +61,7 @@ __sfr __at (0xFE) io_border;
|
||||
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||
const uint8_t n_banks = 5;
|
||||
const uint8_t n_banks = 6;
|
||||
|
||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||
uint8_t pop_cheats;
|
||||
@@ -369,6 +370,12 @@ static void find_start_level_door(uint8_t room)
|
||||
}
|
||||
}
|
||||
|
||||
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
||||
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
||||
* стартовую позицию. Сбрасывается началом игры и прохождением уровня —
|
||||
* ровно как в оригинале (seg003:37 и seg003:413). */
|
||||
static uint8_t pop_checkpoint;
|
||||
|
||||
static void pop_start_level(void)
|
||||
{
|
||||
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
|
||||
@@ -376,6 +383,16 @@ static void pop_start_level(void)
|
||||
uint8_t pos = pop_level_start_pos();
|
||||
int8_t dir = (int8_t)~pop_level_start_dir();
|
||||
uint8_t seq = SEQ_TURN;
|
||||
/* Особое событие: старт с чекпойнта (do_startpos, seg003:0141).
|
||||
* Числа — в pop_tune.h. Плиту снимаем ПОСЛЕ pop_level_reset_tiles()
|
||||
* ниже, иначе рестарт её вернёт. */
|
||||
uint8_t chkp = (uint8_t)(pop_current_level == POP_CHKP_LEVEL && pop_checkpoint);
|
||||
if (chkp) {
|
||||
start_room = POP_CHKP_RESPAWN_ROOM;
|
||||
pos = POP_CHKP_RESPAWN_TILEPOS;
|
||||
dir = (int8_t)~POP_CHKP_RESPAWN_DIR; /* стартует в ПРОТИВОположную
|
||||
* и разворачивается seq_5 */
|
||||
}
|
||||
|
||||
/* Рестарт уровня = load_level() заново (play_level, seg003:57): в
|
||||
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
|
||||
@@ -386,6 +403,12 @@ static void pop_start_level(void)
|
||||
pop_level_reset_guards();
|
||||
pop_guard_reset(); /* start_level: Guard.direction = none */
|
||||
pop_loose_mob_reset(); /* start_level: mobs_count = 0 (seg003:88) */
|
||||
/* «Особое событие: убрать loose-плиту» (do_startpos): без этого путь в
|
||||
* комнату-чекпойнт после респавна остался бы непройденным. */
|
||||
if (chkp)
|
||||
pop_level_set_tile(POP_CHKP_CLEAR_ROOM,
|
||||
(uint8_t)(POP_CHKP_CLEAR_ROW * 10 + POP_CHKP_CLEAR_COL),
|
||||
TILE_EMPTY);
|
||||
|
||||
/* Кнопку жмём ДО отрисовки комнаты — как в оригинале (do_startpos
|
||||
* отрабатывает раньше первого draw_room), иначе шов кадр показывал бы
|
||||
@@ -450,6 +473,7 @@ int main(void)
|
||||
}
|
||||
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
|
||||
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
||||
pop_checkpoint = 0; /* init_game (seg003:37) */
|
||||
|
||||
initgraph();
|
||||
gfx_set_visible_page(0);
|
||||
@@ -618,7 +642,25 @@ int main(void)
|
||||
}
|
||||
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
|
||||
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
|
||||
uint8_t left = (uint8_t)(pop_leave_dir == 1);
|
||||
pop_leave_dir = 0;
|
||||
/* level3_set_chkp (seg002:0665). ВНИМАНИЕ на комнату: оригинал
|
||||
* зовёт его из leave_room ДО goto_other_room, то есть Char.room
|
||||
* там ещё СТАРАЯ. Значит условие `Char.room == 7` читается как
|
||||
* «Кид УХОДИТ ИЗ комнаты 7 влево» (в комнату 2), а не «входит в
|
||||
* неё». Смысл: чекпойнт даётся за то, что комната 7 пройдена
|
||||
* НАСКВОЗЬ, и респавн ставится туда, куда Кид только что попал.
|
||||
*
|
||||
* Сначала я поставил проверку на комнату НАЗНАЧЕНИЯ — и это
|
||||
* поймал пользователь прогоном в SDLPoP: он умер В комнате 7, но
|
||||
* влево из неё не выходил, и оригинал вернул его в стартовую
|
||||
* комнату 9, а плита осталась цела. Заодно тут запоминается
|
||||
* достигнутый потолок HP: смерть после чекпойнта его не отбирает. */
|
||||
if (left && pop_current_level == POP_CHKP_LEVEL &&
|
||||
cur_room == POP_CHKP_ROOM) {
|
||||
pop_checkpoint = 1;
|
||||
hitp_beg_lev = hitp_max;
|
||||
}
|
||||
if (nb) {
|
||||
enter_room(nb);
|
||||
gfx_set_draw_page(back);
|
||||
@@ -659,6 +701,7 @@ int main(void)
|
||||
* поднятая на уровне «жизнь» (большая склянка) переезжает
|
||||
* дальше, а урон — нет (do_startpos дольёт до потолка). */
|
||||
hitp_beg_lev = hitp_max;
|
||||
pop_checkpoint = 0; /* уровень пройден (seg003:413) */
|
||||
if (pop_level_load_num(pop_next_level) != 0) {
|
||||
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
||||
} else {
|
||||
|
||||
@@ -130,7 +130,7 @@ void tk_key_down(uint16_t code)
|
||||
uint8_t tk_modif[30];
|
||||
uint8_t tk_start_room;
|
||||
|
||||
uint8_t *pop_trob_modif(uint8_t room) { (void)room; return tk_modif; }
|
||||
uint8_t *pop_trob_modif(uint8_t room) __banked { (void)room; return tk_modif; }
|
||||
uint8_t pop_level_start_room(void) { return tk_start_room; }
|
||||
|
||||
/* Журнал: что физика попросила у соседей. Тест сверяет ФАКТ и параметры
|
||||
@@ -157,11 +157,11 @@ uint8_t tk_log_count(uint8_t kind)
|
||||
return c;
|
||||
}
|
||||
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
||||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type) __banked
|
||||
{ log_put(TK_TROB, room, tilepos, (uint8_t)type); }
|
||||
|
||||
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||||
uint8_t button_type, uint8_t modifier)
|
||||
uint8_t button_type, uint8_t modifier) __banked
|
||||
{ (void)modifier; log_put(TK_BUTTON, room, tilepos, button_type); }
|
||||
|
||||
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages)
|
||||
|
||||
Reference in New Issue
Block a user