65797af44c
ТАЙМИНГИ. Пользователь заметил, что заклинание Джафара не совпадает с музыкой. Замер в MAME (breakpoint на вспышке + курсор трека): реплика m53 успевала ДОИГРАТЬ до молнии, хотя по шкале оригинала между входом Джафара и заклинанием ровно 822 тика = 13,7 с. Причина: пейсинг считал НАШИ ожидания vsync, а не прошедшее время. Полноэкранная копия страницы стоит больше кадра луча, и разница копилась. Кадровые прерывания для счёта не годятся (теряются в di-окнах акселератора), поэтому часами стал насос CBL: он идёт от расхода буфера железом, 85,4 Гц, и ему безразлично, чем занят главный цикл. Один тик оригинала = 57/40 тика насоса (0,07 % ошибки, без деления в кадре). Когда звук выключен, работает прежний путь по кадрам луча. После фикса замер даёт 229 блоков остатка m53 против расчётных 233 — расхождение 47 мс. АРХИВЫ. Группы ресурсов сложены в PBA1: kid (58 атласов), pv (78), shadow (32), bg (25), title (20), guard (10), vizier (5), skel (4). Было 232 файла на образе, стало 8 архивов плюс шесть палитр и таблицы уровней. При цене `open` 51,4 мс против 32,6 мс за чтение 16 КБ это возвращает секунды на каждой загрузке. - pop_pack_arc.py печатает индексы элементов константами (ARC_<GRP>_<FILE>), порядок задаёт Makefile, а серии код проверяет статически; - atlas_load разделён: atlas_attach принимает уже прочитанную страницу, поэтому чтение из архива не дублирует проверку магии и подготовку W0; - имена архивов живут в pop_arc.c и адресуются номером. Строковый литерал лежит в rodata своего банка, и указатель на него из другого банка после переключения W3 показывает на чужие данные — ровно так падала загрузка фона (поймано брейкпоинтом на puts). Проверено в MAME: заставка, интро и уровень 1 собираются из архивов, Тень грузит все 32 страницы. Host-тесты: 15 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1124 lines
73 KiB
C
1124 lines
73 KiB
C
/*
|
||
* roomtest_cold.c — ХОЛОДНАЯ половина главного цикла, собирается в БАНК
|
||
* (--bank 8=roomtest_cold.c, окно W3). Здесь всё, что отрабатывает раз на
|
||
* уровень или только по отладочной клавише; кадровый путь остался в
|
||
* roomtest.c. Зачем разделили и что осталось снаружи — см. roomtest_cold.h.
|
||
*
|
||
* Правила банка те же, что у guards.c: состояние — в W1/W2 (данные этого
|
||
* модуля тоже, --bank-data не включён), звать можно резидент W1/W2 и другие
|
||
* __banked-модули через трамплины.
|
||
*/
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include "roomtest_cold.h"
|
||
#include "pop_bg.h" /* pop_loose_mob_reset — банк 7, зовём через трамплин */
|
||
#include "pop_kid.h"
|
||
#include "pop_map.h"
|
||
#include "pop_level.h"
|
||
#include "pop_trob.h"
|
||
#include "pop_guard.h"
|
||
#include "pop_state.h"
|
||
#include "pop_geom.h"
|
||
#include "pop_cdraw.h" /* pop_cd — инвалидация слотов после переворота */
|
||
#include <sprite.h> /* gfx_copy_page */
|
||
#include "pop_tune.h"
|
||
#include "pop_ctrl.h" /* pop_ctrl_init/close — ввод поднимает и закрывает boot */
|
||
#include "pop_shadow.h" /* запечённый набор Тени */
|
||
#include "pop_sfx.h" /* звуковые эффекты через CBL */
|
||
#include "pop_vflip.h" /* сброс зеркальных страниц на смене уровня */
|
||
#include "pop_cheat.h" /* pop_cheats — режим разработки включает pop_boot */
|
||
#include "pop_pace.h" /* pop_speed_mode — режим скорости в отладочной метке */
|
||
#include "pop_settings.h" /* pop_settings_defaults — стартовый профиль VANILLA */
|
||
#include "pop_cfg.h" /* pop_cfg_load — POP.CFG поверх дефолтов */
|
||
#include "pop_status.h" /* статус-строка и отладочная полоса — банк 9 */
|
||
#include "pop_menu.h" /* pop_menu_init/free — атлас UI в bank 9 */
|
||
#include "pop_ui.h" /* ранняя чёрная палитра + snapshot стартового экрана */
|
||
#include "pop_pal.h" /* единый lifecycle game/title/level палитр */
|
||
#include "pop_timer.h" /* global 60-minute timer — core in bank 9 */
|
||
#include <stdio.h> /* puts/printf — сообщения об ошибках загрузки */
|
||
#include <kbd_raw.h>
|
||
|
||
#define SEQ_STAND 2
|
||
#define SEQ_TURN 5
|
||
#define SEQ_FALL 7
|
||
#define SEQ_RUN 84
|
||
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
|
||
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
||
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
||
#define TILE_EMPTY 0
|
||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||
|
||
/* Во время смены уровня enter_room должен только СОБРАТЬ новую страницу.
|
||
* Физический flip делает pop_level_switch после окончательной палитры. */
|
||
static uint8_t level_prepare;
|
||
static uint8_t level_prepared_page;
|
||
|
||
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
|
||
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
|
||
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
|
||
* Без этого на уровне 2 Кид материализуется внутри глухой закрытой створки:
|
||
* его стартовый тайл (комната 5, ряд 1, колонка 3) — правая половина
|
||
* той самой двери. */
|
||
static void find_start_level_door(uint8_t room)
|
||
{
|
||
uint8_t *m = pop_trob_modif(room);
|
||
uint8_t tp;
|
||
if (!m) return;
|
||
/* Батч-доступ к странице уровня тут не нужен: функция отрабатывает раз
|
||
* за уровень, а pop_add_trob сам лезет в W0. */
|
||
for (tp = 0; tp < 30; tp++) {
|
||
if (pop_level_tile(room, tp) != TILE_LEVELDOOR_LEFT) continue;
|
||
m[tp] = 43; /* створка поднята полностью */
|
||
pop_add_trob(room, tp, 3); /* ...и тут же поедет ВНИЗ (type>=3) */
|
||
}
|
||
}
|
||
|
||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||
* (seg003:028A).
|
||
*
|
||
* Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define:
|
||
* start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором
|
||
* персонаж должен ОКАЗАТЬСЯ.
|
||
*
|
||
* Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж
|
||
* ставится спиной и доворачивается кадрами позы входа. Сама поза — из
|
||
* tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}):
|
||
* 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2);
|
||
* это оно захлопывает решётку за спиной в первой комнате;
|
||
* 2 (уровень 13) — вбегание (seq_84_run);
|
||
* иначе — разворот на месте (seq_5_turn).
|
||
* Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */
|
||
void pop_start_level(void) __banked
|
||
{
|
||
uint8_t entry_pose, start_room, pos, seq = SEQ_TURN, chkp;
|
||
int8_t dir;
|
||
|
||
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
||
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
|
||
/* upside_down тоже сбрасывается стартом уровня (seg003:38/188). Если
|
||
* картинка была перевёрнута, отрисовка комнаты ниже уже пойдёт обычной
|
||
* стороной, поэтому переворачивать ничего не надо — только снять флаги. */
|
||
pop_upside = 0;
|
||
pop_upside_want = 0;
|
||
pop_upside_dirty = 0;
|
||
entry_pose = pop_level_entry_pose(pop_current_level);
|
||
start_room = pop_level_start_room();
|
||
pos = pop_level_start_pos();
|
||
dir = (int8_t)~pop_level_start_dir();
|
||
/* Особое событие: старт с чекпойнта (do_startpos, seg003:0141).
|
||
* Числа — в pop_tune.h. Плиту снимаем ПОСЛЕ pop_level_reset_tiles()
|
||
* ниже, иначе рестарт её вернёт. */
|
||
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 */
|
||
}
|
||
|
||
#ifdef DBG_START_ROOM
|
||
/* ОТЛАДОЧНЫЙ СТАРТ: сразу в целевую комнату оптимизации, минуя проход
|
||
* уровня. Задаётся сборкой (make ROOM=15 POS=2), по умолчанию — сцена
|
||
* 11/15 из docs/perf_l11_room15.md: Кид в (0,2), справа чомпер под
|
||
* факелом, дальше второй факел и страж.
|
||
*
|
||
* Ставится ПОСЛЕ чекпойнта намеренно: отладочная позиция должна
|
||
* перебивать и его тоже, иначе рестарт уровня уводил бы Кида из
|
||
* измеряемой комнаты. Направление и позу входа не трогаем — они из
|
||
* данных уровня, как у обычного старта. */
|
||
/* ...НО ТОЛЬКО НА СВОЁМ УРОВНЕ. pop_start_level зовётся ещё и на
|
||
* СМЕНЕ уровня, и без этой проверки отладочная позиция перехватывала
|
||
* переход: на 6-м Кид проваливается в комнате 1, а на 7-м обязан
|
||
* появиться в комнате 17 и продолжить падение вниз (спецсобытие
|
||
* «вход падением», см. ниже). Вместо этого он оказывался в
|
||
* DBG_START_ROOM/POS, спецсобытие не срабатывало вовсе, и переход 6->7
|
||
* выглядел сломанным (поймано пользователем 2026-08-20 на сборке
|
||
* make LEVEL=6 ROOM=1 POS=18).
|
||
*
|
||
* Рестарт ТОГО ЖЕ уровня (смерть, чекпойнт) отладочную позицию
|
||
* сохраняет — ради этого она и перебивает чекпойнт выше. */
|
||
if (pop_current_level == FIRST_LEVEL) {
|
||
start_room = DBG_START_ROOM;
|
||
pos = DBG_START_POS;
|
||
}
|
||
#endif
|
||
|
||
/* Рестарт уровня = load_level() заново (play_level, seg003:57): в
|
||
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
|
||
* разбитые плиты целы, выпитые зелья на месте (BUG-RESPAWN-1), и
|
||
* убитые стражи снова живы на своих тайлах (BUG-RESPAWN-2). */
|
||
pop_trob_reset();
|
||
pop_level_reset_tiles();
|
||
pop_level_reset_guards();
|
||
pop_guard_reset(); /* start_level: Guard.direction = none */
|
||
/* united_with_shadow = 0 (seg003:95) — состояние ПЕРСОНАЖА уровня, а не
|
||
* комнаты: pop_guard_reset зовётся и при каждом входе в комнату (из
|
||
* pop_guard_enter), поэтому гасить слияние надо здесь. */
|
||
pop_united_shadow = 0;
|
||
pop_shadow_init = 0;
|
||
pop_guard_notice_timer = 0; /* фора Джафара — не переживает рестарт */
|
||
pop_nav_hold = 0; /* старт уровня снимает и держатель чита */
|
||
pop_loose_mob_reset(); /* start_level: mobs_count = 0 (seg003:88) */
|
||
pop_loose_forget(); /* ...и фазы плит — уровень восстановлен целиком */
|
||
/* «Особое событие: убрать 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), иначе шов кадр показывал бы
|
||
* ещё не тронутое состояние решётки. */
|
||
if (entry_pose == ENTRY_POSE_FALL) {
|
||
/* get_tile(5,2,0) + trigger_button(0,0,-1) — «нажать» кнопку room5,
|
||
* col2, row0 её же типом и модификатором (в оригинале и то и другое
|
||
* берётся из curr_* после get_tile). */
|
||
uint8_t tp = 2; /* row0*10 + col2 */
|
||
pop_trigger_button(5, tp, pop_level_tile(5, tp), pop_trob_modif(5)[tp],
|
||
0 /* seg003:170 — playsound=0 */);
|
||
seq = SEQ_FALL;
|
||
} else if (entry_pose == ENTRY_POSE_RUN) {
|
||
seq = SEQ_RUN;
|
||
}
|
||
|
||
/* Тоже ДО отрисовки комнаты: дверь, через которую вошли, обязана быть
|
||
* открыта уже в первом кадре (в оригинале find_start_level_door стоит
|
||
* между do_startpos и draw_level_first). */
|
||
find_start_level_door(start_room);
|
||
|
||
enter_room(start_room);
|
||
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
|
||
#ifdef DBG_START_ROOM
|
||
/* ...и поправить x: kid_init ставит `x_bump[col] + TILE_SIZEX`, а это
|
||
* ЛЕВАЯ ГРАНИЦА СЛЕДУЮЩЕЙ колонки (порт set_start_pos — в данных уровня
|
||
* стартовые позиции подобраны под такую договорённость). Отладочный
|
||
* старт называет тайл, В КОТОРОМ Кид должен оказаться, поэтому сдвигаем
|
||
* его внутрь названного: с границы физика относит Кида к колонке правее,
|
||
* и в 11/15 он стартовал прямо в челюстях (найдено пользователем
|
||
* 2026-08-19).
|
||
*
|
||
* Отступ ровно 2, а не «половина тайла»: колонку определяет не сам x, а
|
||
* ВЕСОВАЯ ТОЧКА кадра (determine_col -> dx_weight: dx позы минус
|
||
* weight-биты, с учётом направления), поэтому геометрически ровной
|
||
* середины тут нет. Число снято замером живой сцены: x = 98 при позе
|
||
* стойки даёт curr_col = 2, x = 94 — уже 1. */
|
||
if (pop_current_level == FIRST_LEVEL) { /* см. условие выше */
|
||
Kid.x = (uint8_t)(pop_x_bump[(pos % 10) + FIRST_ONSCREEN_COLUMN] +
|
||
TILE_SIZEX - 2);
|
||
Kid.curr_col = (int8_t)(pos % 10);
|
||
}
|
||
#endif
|
||
/* Спецсобытие «вход падением» (set_start_pos, seg003:0196): на 7-м
|
||
* уровне Кид ставится в комнату 17, а экран тут же переводится на
|
||
* комнату ПОД ней (`goto_other_room(3)`: y −= 189, ряд пересчитать) —
|
||
* он влетает в неё сверху, продолжая падение с шестого уровня. */
|
||
if (pop_current_level == FALLING_ENTRY_LEVEL &&
|
||
start_room == FALLING_ENTRY_ROOM) {
|
||
uint8_t below = pop_room_link(start_room, LINK_DOWN);
|
||
if (below) {
|
||
Kid.y = (uint8_t)(Kid.y - 189);
|
||
Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
|
||
enter_room(below);
|
||
}
|
||
}
|
||
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
|
||
/* Остальные одноразовые флаги того же set_start_pos (seg003:028A). Они
|
||
* НЕ косметика: droppedout переживал границу уровня и на следующем
|
||
* уровне заставлял стража идти «за упавшим Кидом» вниз
|
||
* (guard_follows_kid_down) — на 4-м уровне в комнате 22 страж по этому
|
||
* флагу шёл прямо в челюсти, хотя Кида он даже не видел. Взводится он
|
||
* на ПРОШЛОМ уровне: Кид сорвался с уступа в боевой позе (кадры
|
||
* 150..179, pop_map.c start_fall) — обычная концовка боя со скелетом на
|
||
* 3-м. Поймано пользователем 2026-08-24 (переход 3 -> 4 ногами, через
|
||
* дверь уровня; читом Shift+L старт чистый, и баг не воспроизводился). */
|
||
pop_droppedout = 0;
|
||
offguard = 0;
|
||
knock = 0;
|
||
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
|
||
* (=2); исключение — demo level 0, где меч уже выдан. */
|
||
pop_have_sword = (uint8_t)(pop_current_level == 0 || pop_current_level >= 2);
|
||
/* seamless (seg003:158): пришли БЕЗ двери уровня, прямо на бегу — HP не
|
||
* трогаем, оно переезжает как есть. Флаг одноразовый: у оригинала его
|
||
* гасит заставка «LEVEL N», которую этот же переход и пропускает. */
|
||
if (pop_seamless) { pop_seamless = 0; pop_kid_hp_carry(); }
|
||
else pop_kid_hp_reset();
|
||
}
|
||
|
||
/* ---- ОТЛАДКА: обход комнат по номеру -------------------------------- *
|
||
*
|
||
* Нужен, чтобы пройти все комнаты уровня и собрать список багов отрисовки,
|
||
* включая недостижимые обычным путём.
|
||
*
|
||
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
|
||
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen, и
|
||
* модификаторы перечитываются из уровня заново, то есть чит ОТМАТЫВАЕТ МИР
|
||
* — открытые/закрытые ворота, выдвинутые пики, нажатые кнопки. Навигация
|
||
* обязана только телепортировать; исходное состояние даёт перезапуск. */
|
||
uint8_t pop_dbg_roomnav(uint8_t dir) __banked
|
||
{
|
||
uint8_t r, i, c, pos = 20, any = 0xFF;
|
||
|
||
/* Кид МЁРТВ: сначала честный рестарт уровня, потом телепорт. Навигация
|
||
* только переставляет Кида (kid_init + сброс HP), то есть «оживляет» его
|
||
* мимо start_level — а тот обязан вернуть тайлы, модификаторы и ОЧИСТИТЬ
|
||
* слот соперника (seg003:100: Guard.direction = dir_56_none). Без этого
|
||
* после смерти оставался живой скелет рядом с вернувшейся кучей костей. */
|
||
if (pop_kid_dead) pop_start_level();
|
||
|
||
r = cur_room;
|
||
r = (dir == 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
||
: (uint8_t)(r == 1 ? 24 : r - 1);
|
||
enter_room(r);
|
||
|
||
/* Куда поставить Кида в новой комнате: пол С МАКСИМАЛЬНЫМ Y — сначала
|
||
* НИЖНИЙ ряд (2), потом 1, и только потом 0 (пользователь 2026-08-11).
|
||
* Раньше брался первый пол подряд, то есть почти всегда (0,0) — верхний
|
||
* левый угол: это и «прилёт» в проём/ворота, и, на уровне 5, посадка
|
||
* вплотную к тени спецсобытия, которая ждёт в колонке −1 ряда 0. Вместо
|
||
* сцены кражи зелья начиналась схватка, которой в этой комнате быть не
|
||
* должно. Снизу вверх нужно и самой навигации: в верхнем ряду пола чаще
|
||
* нет, и Кид проваливается из комнаты сразу после телепорта.
|
||
*
|
||
* ИСКЛЮЧЕНИЕ — комната 14 уровня 7 (пользователь 2026-08-12): её проходят
|
||
* СВЕРХУ ВНИЗ (с ряда 0 на ряд 2 через зацеп за кромки), и с нижнего ряда
|
||
* этот спуск не проверить вовсе. Пол в ряду 0 там есть, так что Кид не
|
||
* проваливается.
|
||
*
|
||
* В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его там нет —
|
||
* любой проходимый tile_is_floor (seg001: всё, кроме пустоты, верха
|
||
* колонны, верха двери, стены и «половинок» 26..29). */
|
||
{
|
||
int8_t row, step = -1, first = 2;
|
||
if (pop_current_level == GRAB_DOWN_LEVEL &&
|
||
r == GRAB_DOWN_ROOM) { first = 0; step = 1; }
|
||
for (row = first; row >= 0 && row <= 2 && any == 0xFF;
|
||
row = (int8_t)(row + step)) {
|
||
uint8_t fallback = 0xFF;
|
||
for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) {
|
||
c = (uint8_t)(room_fg[i] & 0x1F);
|
||
if (c == 1) { any = i; break; }
|
||
if (fallback == 0xFF && c && c != 9 && c != 12 &&
|
||
c != 20 && !(c >= 26 && c <= 29)) fallback = i;
|
||
}
|
||
if (any == 0xFF) any = fallback;
|
||
}
|
||
}
|
||
if (any != 0xFF) pos = any;
|
||
kid_init(SEQ_STAND, (int8_t)(pos % 10), (int8_t)(pos / 10), 0);
|
||
pop_kid_hp_reset();
|
||
|
||
/* ВЫХОД ИЗ БОЯ (BUG-CHEAT-FIGHT-1). Телепорт с вынутым мечом заклинивал
|
||
* управление намертво: pop_control при Kid.sword == SWORD_2_DRAWN уходит
|
||
* в control_with_sword, а там единственный выход — ветка «убрать меч» на
|
||
* кадре FRAME_171_STAND_WITH_SWORD. После kid_init(SEQ_STAND) кадр
|
||
* обычный, стража в новой комнате нет, swordfight не запускается — и
|
||
* диспетчер каждый кадр не делает НИЧЕГО. Это болезнь ЧИТА: в оригинале
|
||
* телепорта между комнатами нет, попасть в режим боя без соперника
|
||
* нечем. Меч в инвентаре (pop_have_sword) НЕ теряем — только состояние
|
||
* схватки. */
|
||
Kid.sword = SWORD_0_SHEATHED;
|
||
holding_sword = 0;
|
||
offguard = 0;
|
||
guard_refrac = 0;
|
||
|
||
/* ПРОВАЛ НА СЛЕДУЮЩИЙ УРОВЕНЬ ПО ЧИТУ (та же болезнь, что выше).
|
||
* Уровень 12 кончается ФАКТОМ попадания в комнату 23 — двери у него нет
|
||
* (pop_seamless, seg000:0900). А навигация перебирает комнаты ПО
|
||
* НОМЕРУ, то есть любой обход рано или поздно наступает на 23-ю, и
|
||
* уровень молча меняется на 13-й: комнату 23 двенадцатого уровня читом
|
||
* не посмотреть в принципе, а прогон «съезжает» на соседний уровень
|
||
* незаметно для проверяющего (поймано 2026-08-13 на первом же прогоне).
|
||
* Держим триггер, пока Кид не сменит комнату НОГАМИ. */
|
||
pop_nav_hold = 1;
|
||
return 1;
|
||
}
|
||
|
||
|
||
/* ---- ЧИТЫ (seg000: switch под if (cheats_enabled)) -------------------- *
|
||
* Переехали из главного цикла в банк 8: дёргаются раз в кадр, то есть
|
||
* один трамплин на кадр, а места в резиденте W1/W2 не осталось (звук,
|
||
* атлас Тени и пейсинг съели остаток; см. TASKS_OPEN, MEM).
|
||
*
|
||
* Флаги «фронта» стали статиками модуля — раньше они были локалями main.
|
||
* Смысл прежний: одно нажатие = одно срабатывание.
|
||
*/
|
||
static uint8_t cheat_prev, immo_prev, sound_prev, nlvl_prev;
|
||
static uint8_t upsd_prev, xdec_prev, xinc_prev, cfg_prev;
|
||
static uint8_t time_sub_prev, time_add_prev;
|
||
|
||
void pop_cheat_tick(void) __banked
|
||
{
|
||
/* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
||
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
||
* K — убить стража;
|
||
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
|
||
* иначе боёвку проверять получается ровно три раза);
|
||
* Ctrl+S — вкл/выкл звук (порт Ctrl+S из SDLPoP, seg000:657).
|
||
* НЕ чит: работает и без pop_cheats, как в оригинале.
|
||
* Ctrl обязателен — голая S у нас свободна, но менять
|
||
* пользовательскую раскладку оригинала незачем;
|
||
* Shift+L — следующий уровень (seg000:698, ветка next_level =
|
||
* current_level + 1). Единственный вменяемый способ
|
||
* тестировать уровни 2..14. NB: L без Shift — отладочный
|
||
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
|
||
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
|
||
* что конфликта на практике нет;
|
||
* F7 / F8 — отнять / добавить минуту. Это точный аналог
|
||
* keypad −/+ SDLPoP, но +/- заняты обходом комнат;
|
||
* [ / ] — подвинуть Кида на ПИКСЕЛЬ влево/вправо (seg000:1828,
|
||
* `--Char.x` / `++Char.x`). Нужен потому, что мост MAME
|
||
* теряет нажатия при быстрой отправке, и подогнать Кида в
|
||
* нужную позу скриптом нельзя (на это упёрлись BUG-LOOSE-2
|
||
* и BUG-GATE-PASS-1). Пишем прямо в Kid.x: геометрию
|
||
* персонажа в этом порте задаёт не диспетчер (грабли
|
||
* L1-EXIT). Колонку не трогаем — её пересчитает физика
|
||
* следующего кадра. */
|
||
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
||
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
||
uint8_t s = (uint8_t)(kbd_raw_down(KBD_SOUND_TOGGLE) &&
|
||
(kbd_raw_down(KBD_LCTRL) || kbd_raw_down(KBD_RCTRL)));
|
||
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
|
||
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
|
||
uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE);
|
||
uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC);
|
||
uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC);
|
||
uint8_t ts = kbd_raw_down(KBD_CHEAT_TIME_SUB);
|
||
uint8_t ta = kbd_raw_down(KBD_CHEAT_TIME_ADD);
|
||
/* C — записать POP.CFG сейчас (отладка MS2, до экрана Settings:
|
||
* там сохранять будет Apply/выход). */
|
||
uint8_t cf = kbd_raw_down(0x21); /* C (PS/2 set 2) */
|
||
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||
/* Бессмертие по кругу: 0 выкл -> 1 «только бой» -> 2 «бой и
|
||
* мелкий урон» -> 0. Что означают уровни — pop_state.h. */
|
||
if (pop_cheats && i && !immo_prev)
|
||
pop_immortal = (uint8_t)(pop_immortal >= 2 ? 0 : pop_immortal + 1);
|
||
if (s && !sound_prev) {
|
||
pop_sfx_toggle(); /* не под pop_cheats */
|
||
pop_settings.sound_enabled = pop_snd_want;
|
||
/* Порт seg000:657: оригинал отвечает строкой в статус-полосе. */
|
||
pop_status_msg = (uint8_t)(pop_snd_want ? POP_MSG_SOUND_ON
|
||
: POP_MSG_SOUND_OFF);
|
||
}
|
||
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
|
||
pop_next_level = (uint8_t)(pop_current_level + 1);
|
||
/* U — переворот экрана (seg000:0793 — чит и в оригинале).
|
||
* Тот же путь, что у зелья типа 4: флаг + пометка «перевернулись». */
|
||
if (pop_cheats && up && !upsd_prev) { /* тот же путь, что у зелья */
|
||
pop_upside_want = (uint8_t)!pop_upside_want;
|
||
pop_upside_dirty = 1;
|
||
}
|
||
if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1);
|
||
if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1);
|
||
if (pop_cheats && ts && !time_sub_prev) pop_timer_cheat_subtract();
|
||
if (pop_cheats && ta && !time_add_prev) pop_timer_cheat_add();
|
||
if (pop_cheats && cf && !cfg_prev) (void)pop_cfg_save();
|
||
cheat_prev = k;
|
||
immo_prev = i;
|
||
sound_prev = s;
|
||
nlvl_prev = nl;
|
||
upsd_prev = up;
|
||
xdec_prev = xd;
|
||
xinc_prev = xi;
|
||
time_sub_prev = ts;
|
||
time_add_prev = ta;
|
||
cfg_prev = cf;
|
||
}
|
||
|
||
/* Отладочная строка в верхнем борте переехала в pop_status.c (банк 9):
|
||
* она теперь ТЕКСТОВАЯ, а шрифт живёт там же. Здесь не осталось ничего —
|
||
* см. заголовок pop_status.h про то, почему заявка сообщения стоит один
|
||
* байт, а весь рендер сидит в банке. */
|
||
|
||
/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ
|
||
* страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет
|
||
* активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */
|
||
/* side — куда ушёл КИД (0 left, 1 right, 2 up, 3 down); 0xFF = «не переход
|
||
* за Кидом» (старт уровня, респавн, чит-навигация). От него зависит ровно
|
||
* одно: пойдёт ли страж следом (pop_guard_follow, порт exit_room+
|
||
* follow_guard) или останется в прежней комнате. */
|
||
void enter_room_side(uint8_t room, uint8_t side) __banked
|
||
{
|
||
uint8_t pg;
|
||
/* Страж идёт за Кидом? — решаем ДО того, как что-либо загрузим: функции
|
||
* нужны и старая комната (Guard.room), и целевая. Пошёл — ни leave, ни
|
||
* enter звать нельзя: он уже перенесён и живёт дальше как есть. */
|
||
uint8_t follow = (uint8_t)(side <= 3 ? pop_guard_follow(room, side) : 0);
|
||
if (!follow) pop_guard_leave(); /* запомнить стража уходящей комнаты */
|
||
/* ...и так же запомнить НЕДОСЧИТАННЫЕ loose: пока карта ещё описывает
|
||
* СТАРУЮ комнату, фаза уезжает в персистентный room_modif + trob, и
|
||
* плита доваливается заочно (см. pop_loose_leave_room / animate_loose). */
|
||
pop_loose_leave_room();
|
||
uint8_t *room_mod;
|
||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
|
||
pop_map_set(room_fg);
|
||
/* Соседей слева/справа карта забирает из уровня САМА (целиком, см.
|
||
* pop_map_set_edges) — отдельной копии в приложении нет. */
|
||
pop_map_set_edges(pop_room_link(room, 0), pop_room_link(room, 1),
|
||
pop_room_link(room, 2), pop_room_link(room, 3));
|
||
/* Боковой переход: колонки сдвинулись ровно на 10 (x ∓140), флаги
|
||
* перекрытия при этом инвариантны — историю перенумеровываем, а не
|
||
* теряем. Без этого решётка шва не бампит на кадре входа и Кид с
|
||
* разбега проходит сквозь закрытые ворота (BUG-GATE-PASS-1). */
|
||
if (side == 0) pop_coll_shift(+10); /* ушёл ВЛЕВО: col += 10 */
|
||
else if (side == 1) pop_coll_shift(-10); /* ушёл ВПРАВО: col −= 10 */
|
||
pop_room_set_below(below_fg);
|
||
/* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика
|
||
* уровня, mod — живой room_modif (ленивая инициализация). */
|
||
{
|
||
uint8_t up = pop_room_link(room, 2), c;
|
||
if (up) {
|
||
uint8_t *um = pop_trob_modif(up);
|
||
pop_level_access_begin();
|
||
for (c = 0; c < 10; c++) above_fg[c] = pop_level_tile_raw(up, (uint8_t)(20 + c));
|
||
pop_level_access_end();
|
||
for (c = 0; c < 10; c++) above_mod[c] = um[20 + c];
|
||
pop_room_set_above(above_fg, above_mod);
|
||
pop_map_set_above(above_fg); /* плита-потолок: пробой снизу + провал */
|
||
} else {
|
||
pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */
|
||
pop_map_set_above(0);
|
||
}
|
||
}
|
||
pop_map_set_room(room);
|
||
/* Kid.room — как в оригинале: «комната персонажа». Полная модель
|
||
* (Kid.room ≠ отрисованной у шва) ещё не портирована, но сравнение
|
||
* Kid.room == Guard.room нужно уже сейчас: без него луч видимости
|
||
* стража никогда не срабатывает. */
|
||
Kid.room = room;
|
||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||
pop_bake_forget(); /* и слоты «первая запечка была на странице N» */
|
||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||
pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */
|
||
pop_check_fall_flo(); /* спецсобытие ур.13: гряда плит сверху */
|
||
/* check_shadow (seg002:0064) стоит ПЕРЕД enter_guard и на «шадоу»-
|
||
* уровнях забирает слот соперника себе: вернула 1 — стража поднимать
|
||
* НЕЛЬЗЯ (в комнате 24 уровня 5 он есть в данных, но оригинал его туда
|
||
* не пускает). Порядок ровно как в оригинале, не «сначала страж, потом
|
||
* заменим». */
|
||
if (!follow && !pop_check_shadow(room))
|
||
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
|
||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||
* а не по свежему backtable. Фаза P0. */
|
||
room_mod = pop_trob_modif(room);
|
||
/* Модификаторы швов — из ЖИВОГО room_modif соседей (openness ворот,
|
||
* видимых через шов, персистентен: ворота room8(0,9) видны в col0 room6).
|
||
* pop_room_load заполнил lcol_bg/rcol_bg статикой уровня — перекрываем. */
|
||
{
|
||
uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1);
|
||
if (lft) { uint8_t *m = pop_trob_modif(lft), i;
|
||
for (i = 0; i < 3; i++) {
|
||
lcol_bg[i] = m[9 + i * 10];
|
||
/* шов рисуется в обе страницы ниже — синхр. сигнатуру */
|
||
seam_sig[i] = seam_row_sig(m, lcol_fg, i);
|
||
} }
|
||
if (rgt) { uint8_t *m = pop_trob_modif(rgt);
|
||
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; }
|
||
}
|
||
seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */
|
||
/* ENTER-ROOM-FAST: комната рисуется ОДИН раз — в скрытую страницу, — и
|
||
* обычно тут же показывается флипом; вторая страница дабл-буфера получает её
|
||
* КОПИЕЙ акселератора (gfx_copy_page, ~384 burst-строки против почти
|
||
* полумиллиона тактов на вторую отрисовку). Побочный эффект важнее
|
||
* тактов: игрок больше не видит, КАК рисуются тайлы — только готовый
|
||
* кадр.
|
||
*
|
||
* Копия идёт банком 0x50, то есть обновляет и VRAM, и ОЗУ-копию
|
||
* страницы-приёмника — иначе heal на ней восстанавливал бы фон прошлой
|
||
* комнаты (инвариант gfx_copy_page, регресс tests/pageflip).
|
||
*
|
||
* Видимая страница здесь МЕНЯЕТСЯ, поэтому главный цикл перечитывает её
|
||
* в начале каждого кадра (front = gfx_get_visible_page()), а не ведёт
|
||
* свой счёт. */
|
||
{
|
||
gfx_rect_t full;
|
||
pg = (uint8_t)(gfx_get_visible_page() ^ 1); /* скрытая */
|
||
gfx_set_draw_page(pg);
|
||
pop_screen_fill_field();
|
||
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||
if (level_prepare) {
|
||
/* Межуровневый staging: пока pop_pal_level_load последовательно
|
||
* пишет KID.PAL и env/wall, видима нулевая чёрная страница.
|
||
* Показывать эту комнату разрешено только после финальной
|
||
* палитры — иначе level 4 успевает мелькнуть цветами level 3. */
|
||
level_prepared_page = pg;
|
||
} else {
|
||
gfx_set_visible_page(pg); /* показать готовое */
|
||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */
|
||
}
|
||
}
|
||
cur_room = room;
|
||
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
|
||
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
|
||
}
|
||
|
||
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||
void enter_room(uint8_t room) __banked { enter_room_side(room, 0xFF); }
|
||
|
||
void pop_qsave_restore_room(uint8_t room) __banked
|
||
{
|
||
uint8_t i, pg, *room_mod;
|
||
gfx_rect_t full;
|
||
uint8_t lft, rgt, up;
|
||
|
||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg,
|
||
rcol_fg, rcol_bg, below_fg);
|
||
pop_map_set(room_fg);
|
||
lft = pop_room_link(room, 0); rgt = pop_room_link(room, 1);
|
||
up = pop_room_link(room, 2);
|
||
pop_map_set_edges(lft, rgt, up, pop_room_link(room, 3));
|
||
pop_room_set_below(below_fg);
|
||
if (up) {
|
||
uint8_t *um = pop_trob_modif(up);
|
||
pop_level_access_begin();
|
||
for (i = 0; i < 10; i++)
|
||
above_fg[i] = pop_level_tile_raw(up, (uint8_t)(20 + i));
|
||
pop_level_access_end();
|
||
for (i = 0; i < 10; i++) above_mod[i] = um[20 + i];
|
||
pop_room_set_above(above_fg, above_mod);
|
||
pop_map_set_above(above_fg);
|
||
} else {
|
||
pop_room_set_above(0, 0); pop_map_set_above(0);
|
||
}
|
||
pop_map_set_room(room);
|
||
pop_loose_reset();
|
||
pop_trob_room_changed();
|
||
pop_bake_forget();
|
||
pop_loose_mob_room_changed();
|
||
room_mod = pop_trob_modif(room);
|
||
if (lft) {
|
||
uint8_t *m = pop_trob_modif(lft);
|
||
for (i = 0; i < 3; i++) {
|
||
lcol_bg[i] = m[9 + i * 10];
|
||
seam_sig[i] = seam_row_sig(m, lcol_fg, i);
|
||
}
|
||
}
|
||
if (rgt) {
|
||
uint8_t *m = pop_trob_modif(rgt);
|
||
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20];
|
||
}
|
||
seam_redraw = seam_rows = 0;
|
||
pg = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||
gfx_set_draw_page(pg);
|
||
pop_screen_fill_field();
|
||
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||
gfx_set_visible_page(pg);
|
||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
cur_room = room; kid_room = Kid.room;
|
||
/* Обе страницы теперь содержат только свежий фон. Старые габариты
|
||
* персонажей относятся к кадру до QuickLoad и не должны запускать heal
|
||
* поверх восстановленной комнаты. */
|
||
pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0;
|
||
pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0;
|
||
pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0;
|
||
pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0;
|
||
pop_cd_init();
|
||
pop_hp_invalidate();
|
||
}
|
||
|
||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
||
* для запуска, и для рестарта после смерти / выпадения из уровня: в
|
||
* оригинале это буквально один и тот же путь (play_level_2 зовёт
|
||
* start_level заново), поэтому и у нас не три копии четырёх строк.
|
||
*
|
||
* Позиция берётся ИЗ ДАННЫХ уровня (pop_level_start_*), а не из #define:
|
||
* start_pos — тайл 0..29 (row*10+col), start_dir — направление, в котором
|
||
* персонаж должен ОКАЗАТЬСЯ.
|
||
*
|
||
* Направление ИНВЕРТИРУЕТСЯ (`Char.direction = ~level.start_dir`): персонаж
|
||
* ставится спиной и доворачивается кадрами позы входа. Сама поза — из
|
||
* tbl_entry_pose (data.h:848 = {0,1,0,…,2,…}):
|
||
* 1 (уровень 1) — падение внутрь (seq_7_fall) + нажатие кнопки room5(0,2);
|
||
* это оно захлопывает решётку за спиной в первой комнате;
|
||
* 2 (уровень 13) — вбегание (seq_84_run);
|
||
* иначе — разворот на месте (seq_5_turn).
|
||
* Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */
|
||
#define SEQ_TURN 5
|
||
#define SEQ_FALL 7
|
||
#define SEQ_RUN 84
|
||
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
|
||
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
||
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
||
|
||
|
||
/* ---- Порядок «Kid vs страж» (BUG-DRAWORDER-1) -----------------------
|
||
*
|
||
* В оригинале никто не «поверх» по определению: и Кид, и страж попадают в
|
||
* midtable при обработке СВОЕГО тайла (set_objtile_at_char, seg006:13F3), а
|
||
* тайлы обходятся в фиксированном порядке — redraw_needed_tiles (seg008:1B06)
|
||
* идёт рядами 2,1,0 и внутри ряда колонками 0..9. Кто нарисован ПОЗЖЕ — тот
|
||
* поверх. Отсюда «Кид проходит ЗА телом стража»: тело лежит правее и/или
|
||
* выше, его тайл в обходе позже. Совпали тайлы — решает sort_curr_objs
|
||
* (seg008:203C): у обычных объектов (не тень, не плита) ниже по obj_y = позже.
|
||
*
|
||
* Мы рисовали страж→Кид всегда, то есть Кид был поверх безусловно.
|
||
*
|
||
* Ключ обхода: чем больше, тем позже. Ряды идут 2,1,0 → (2 - row). */
|
||
#define OBJ_ORDER_KEY(row, col) ((int)(2 - (row)) * 10 + (int)(col))
|
||
|
||
/* get_tile_div_mod (seg006:750): колонка комнаты по X (БЕЗ поправки −7). */
|
||
static int8_t tile_div_mod(int x)
|
||
{
|
||
int v = x - 58; /* pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||
int c = v / TILE_SIZEX;
|
||
if (v % TILE_SIZEX < 0) --c;
|
||
return (int8_t)c;
|
||
}
|
||
|
||
/* char_x_left из set_char_collision (seg006:0723) — левая граница габарита.
|
||
* ВАЖНО: величина НЕ утоньшённая; поправку THIN оригинал применяет только к
|
||
* *_coll, а колонки char_col_left/right считает по этой. */
|
||
static int char_x_left_of(uint8_t x, int8_t dir, int8_t dx,
|
||
uint8_t flags, uint16_t w)
|
||
{
|
||
int e = (int)x + ((dir < 0) ? -dx : dx); /* char_dx_forward */
|
||
uint8_t fl = flags;
|
||
if (dir < 0) fl ^= 0x80;
|
||
/* odd-pixel + усечение деления к нулю — та же поправка, что в pop_map */
|
||
if (!(fl & 0x80) && e <= 57) e++;
|
||
if (dir >= 0) e -= (int)((w + 1) >> 1); /* char_width_half */
|
||
return e;
|
||
}
|
||
|
||
/* set_objtile_at_char (seg006:13F3) — ТАЙЛ ОБЪЕКТА персонажа.
|
||
*
|
||
* Ветка actions_1_run_jump обязательна, а не «упрощаемая» (как я решил
|
||
* сначала): в беге оригинал берёт НЕ curr_row/curr_col, а нижний ряд и ЛЕВУЮ
|
||
* колонку ГАБАРИТА. При беге вправо левая колонка меньше curr_col, поэтому
|
||
* бегущий Кид уходит ЗА объекты справа от себя. Без этой ветки часть кадров
|
||
* бега рисовала Кида поверх лежащего стража. Считаем одинаково для обоих:
|
||
* enter_guard (seg002) тоже ставит стражу action = 1, и односторонний учёт
|
||
* дал бы перекос в другую сторону. */
|
||
static void objtile_at_char(int8_t *row, int8_t *col,
|
||
const pop_char_t *ch, int8_t dx, int8_t dy,
|
||
uint8_t flags, uint16_t w)
|
||
{
|
||
int8_t r = ch->curr_row, c = ch->curr_col;
|
||
if (ch->action == 1) { /* actions_1_run_jump */
|
||
int8_t br = pop_y_to_row((int16_t)((int)ch->y + dy));
|
||
if (br == -1) br = 3; /* char_bottom_row */
|
||
r = br;
|
||
c = tile_div_mod(char_x_left_of(ch->x, ch->direction, dx, flags, w));
|
||
if (c < 0) c = 0; /* MAX(char_col_left, 0) */
|
||
}
|
||
if ((ch->frame >= 135 && ch->frame < 149) ||
|
||
ch->action == 2 || ch->action == 3 ||
|
||
ch->action == 4 || ch->action == 6) c--;
|
||
*row = r; *col = c;
|
||
}
|
||
|
||
/* ВРЕМЕННО: трасса kidobj — порт отладочного вывода, добавленного
|
||
* пользователем в SDLPoP add_kid_to_objtable (seg008:1679):
|
||
*
|
||
* DBG kidobj tilepos=%d frame=%d act=%d col=%d cols=%d..%d rows=%d..%d
|
||
*
|
||
* Считаем ровно то же, что set_char_collision (seg006:0723) +
|
||
* set_objtile_at_char (seg006:13F3): габарит НЕутоньшённый (THIN правит
|
||
* только *_coll, а колонки берутся по «толстой» границе), колонки
|
||
* клампятся в 0..9, нижний ряд −1 читается как 3. Звать ПОСЛЕ отрисовки
|
||
* Кида: ширину/высоту кадра (fpw/fph) заполняет она.
|
||
*
|
||
* Вывод забирает брейкпоинт MAME на резидентном pop_dbg_trap — сама
|
||
* функция банковая, её адрес в W3 неоднозначен. */
|
||
void pop_dbg_kidobj(void) __banked
|
||
{
|
||
int8_t dx = kid_cur_dx(), dy = kid_cur_dy();
|
||
uint8_t fl = kid_cur_flags();
|
||
uint16_t w = pop_cd[POP_CH_KID].fpw;
|
||
uint16_t h = pop_cd[POP_CH_KID].fph;
|
||
int xl = char_x_left_of(Kid.x, Kid.direction, dx, fl, w);
|
||
int xr = xl + (int)((w + 1) >> 1); /* + char_width_half */
|
||
int oy = (int)Kid.y + dy;
|
||
int ty = oy - (int)h + 1;
|
||
int8_t r, c, tr, br, cl, cr;
|
||
objtile_at_char(&r, &c, &Kid, dx, dy, fl, w);
|
||
br = pop_y_to_row((int16_t)oy);
|
||
if (br == -1) br = 3;
|
||
if (ty >= 192) ty = 0;
|
||
tr = pop_y_to_row((int16_t)ty);
|
||
cl = tile_div_mod(xl); if (cl < 0) cl = 0;
|
||
cr = tile_div_mod(xr); if (cr > 9) cr = 9;
|
||
pop_dbg_obj[0] = (int8_t)(r * 10 + c);
|
||
pop_dbg_obj[1] = (int8_t)Kid.frame;
|
||
pop_dbg_obj[2] = (int8_t)Kid.action;
|
||
pop_dbg_obj[3] = Kid.curr_col;
|
||
pop_dbg_obj[4] = cl;
|
||
pop_dbg_obj[5] = cr;
|
||
pop_dbg_obj[6] = tr;
|
||
pop_dbg_obj[7] = br;
|
||
pop_dbg_obj[8] = (int8_t)cur_room;
|
||
pop_dbg_trap();
|
||
}
|
||
|
||
/* 1 = страж рисуется ПОСЛЕ Кида (поверх). __banked: зовётся раз в кадр,
|
||
* то есть один трамплин — а 500 Б кода в резиденте дороже. */
|
||
uint8_t guard_over_kid(void) __banked
|
||
{
|
||
int8_t kr, kc, gr, gc;
|
||
int kk, gk;
|
||
if (Guard.charid == 0 || Guard.room != cur_room) return 0;
|
||
objtile_at_char(&kr, &kc, &Kid, kid_cur_dx(), kid_cur_dy(),
|
||
kid_cur_flags(), pop_cd[POP_CH_KID].fpw);
|
||
objtile_at_char(&gr, &gc, &Guard, pop_gframe.dx, pop_gframe.dy,
|
||
pop_gframe.flags, pop_cd[POP_CH_OPP].fpw);
|
||
kk = OBJ_ORDER_KEY(kr, kc);
|
||
gk = OBJ_ORDER_KEY(gr, gc);
|
||
if (gk != kk) return (uint8_t)(gk > kk);
|
||
/* один тайл — ниже по obj_y рисуется позже (sort_curr_objs) */
|
||
return (uint8_t)((int)Guard.y + pop_gframe.dy > pop_cd[POP_CH_KID].fpy);
|
||
}
|
||
|
||
|
||
/* ---- Переворот уже нарисованного (зелье инверсии, уровень 9) --------- *
|
||
*
|
||
* Порт need_redraw_because_flipped (seg000:15E9 + 939/946). Оригинал
|
||
* держит готовый offscreen и переворачивает его на выводе; у нас страниц
|
||
* ровно две, рабочей третьей нет, поэтому переворачиваем то, что уже
|
||
* нарисовано, — построчной копией акселератора (gfx_copy_page).
|
||
*
|
||
* Почему это законно и почему heal после не ломается: accel ЧИТАЕТ
|
||
* ОЗУ-копию, значит копируется ЧИСТЫЙ ФОН без персонажей, а пишет банком
|
||
* 0x50 — и в видео, и в копию приёмника. После двух проходов обе страницы
|
||
* дабл-буфера содержат перевёрнутый фон в обеих плоскостях, и heal
|
||
* восстанавливает уже его (memory accel_port_y_between_triggers,
|
||
* регресс — tests/pageflip).
|
||
*
|
||
* Порядок: перевернуть в СКРЫТУЮ, показать её, скопировать оттуда во
|
||
* вторую (теперь скрытую) — прямой копией. Так игрок видит результат
|
||
* сразу, а не после второго прохода.
|
||
*
|
||
* Переворачиваем ТОЛЬКО игровое поле (POP_PLAYFIELD_H): полоса HP лежит
|
||
* ниже него и в оригинале тоже не переворачивается. */
|
||
void pop_flip_screen(void) __banked
|
||
{
|
||
gfx_rect_t area;
|
||
uint8_t front = gfx_get_visible_page();
|
||
uint8_t back = (uint8_t)(front ^ 1);
|
||
uint8_t i;
|
||
|
||
area.x = 0; area.y = POP_YOFF;
|
||
area.w = 320; area.h = POP_PLAYFIELD_H;
|
||
|
||
/* СНАЧАЛА убрать из фона то, что в нём запечено, но фоном не является:
|
||
* пламя факелов (pop_torch_wipe объясняет, почему оно вообще там).
|
||
* Отражённое вместе с фоном, оно оседало в зеркальных позициях, где его
|
||
* никто не перерисовывает — по комнате оставались лишние языки огня
|
||
* (нашёл пользователь на глотке зелья).
|
||
*
|
||
* Чистим ОБЕ страницы: источник отражения — видимая, а вторая получит
|
||
* прямую копию уже чистой картинки. Дешевле полной перерисовки комнаты
|
||
* на порядок: 0..4 тайла против 6.2 млн тактов (14.5 кадра, замер MAME
|
||
* 2026-08-12) — а копия отражения стоит всего 2.1 кадра. */
|
||
/* ВНИМАНИЕ на систему координат: pop_upside к этому моменту УЖЕ
|
||
* переключён (главный цикл делает это перед вызовом), а картинка на
|
||
* экране нарисована ещё в СТАРОЙ. Чистить её надо по старым
|
||
* координатам, иначе чёрный бар ложится в зеркальное место — на экране
|
||
* оставался чёрный квадрат, а пламя так и не стиралось. */
|
||
pop_upside = (uint8_t)!pop_upside; /* временно: система ЭКРАНА */
|
||
gfx_set_draw_page(front);
|
||
for (i = 0; i < 30; i++) {
|
||
uint8_t t = room_fg[i];
|
||
if (t == 19 || t == 30) /* факел / факел с щебнем */
|
||
pop_torch_wipe(i / 10, i % 10);
|
||
}
|
||
pop_upside = (uint8_t)!pop_upside; /* вернуть новое состояние */
|
||
|
||
gfx_set_draw_page(back);
|
||
gfx_copy_page(&area, GFX_COPY_VFLIP); /* источник — видимая страница */
|
||
gfx_set_visible_page(back);
|
||
|
||
gfx_set_draw_page(front);
|
||
gfx_copy_page(&area, GFX_COPY_DIRECT); /* вторую страницу — прямой копией */
|
||
|
||
/* Всё, что рисуется поверх фона, надо положить заново: прошлые
|
||
* прямоугольники персонажей относятся к СТАРОЙ (неперевёрнутой)
|
||
* картинке, и heal по ним стёр бы не то место. */
|
||
pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0;
|
||
pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0;
|
||
pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0;
|
||
pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0;
|
||
POP_CD_TOUCH_ALL(); /* фон трогали целиком */
|
||
pop_hp_invalidate(); /* полосу HP перерисовать */
|
||
}
|
||
|
||
/* ---- Запуск, смена уровня, завершение ------------------------------- *
|
||
* Три куска главного цикла, которые отрабатывают раз за игру (или раз на
|
||
* уровень). В резиденте от них оставались только вызовы — сам код здесь.
|
||
*/
|
||
|
||
int pop_boot(void) __banked
|
||
{
|
||
/* Настройки: дефолты, затем POP.CFG, если он есть и корректен. */
|
||
pop_settings_defaults();
|
||
pop_cfg_load();
|
||
pop_timer_new_game(); /* default 60:00, Settings may freeze it */
|
||
|
||
/* Экран Sprinter хранит VRAM и палитры между программами. Перейти в
|
||
* графику надо ДО долгих чтений, но первой же операцией погасить ОБЕ
|
||
* палитры: тогда ни старый экран, ни частично собранный первый уровень
|
||
* не могут мелькнуть перед первым title fade-in. */
|
||
/* PIC-палитры графики живут и в text mode. Предзапись чёрного сокращает
|
||
* окно между аппаратным SETVMOD и первой строкой нашего кода, в котором
|
||
* иначе успевает мелькнуть VRAM предыдущей программы. */
|
||
pop_pal_black();
|
||
initgraph_black();
|
||
/* initgraph_black уже обнулил VRAM до SETVMOD. Повторная заливка после
|
||
* него фиксирует и shadow-копии, откуда затем работают heal/page-copy. */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
gfx_set_draw_page(0);
|
||
bar(0, 0, 319, 255);
|
||
gfx_set_draw_page(1);
|
||
bar(0, 0, 319, 255);
|
||
gfx_set_visible_page(0);
|
||
pop_pal_black();
|
||
|
||
/* Набор фона — по типу СТАРТОВОГО уровня; atlas_load безопасен и после
|
||
* initgraph: при первом вызове он сохраняет страницу DSS W0. Уровень к
|
||
* этому моменту ещё не прочитан, поэтому тип берём напрямую от номера. */
|
||
pop_current_level = FIRST_LEVEL;
|
||
if (pop_bg_load(pop_level_type()) != 0) {
|
||
puts("pop_bg_load failed");
|
||
return -1;
|
||
}
|
||
/* pop_bg_load установил только свои слоты палитры. До конца загрузки
|
||
* всё равно держим экран чёрным. */
|
||
pop_pal_black();
|
||
pop_cd_init(); /* метка «фон трогали» — пустые диапазоны */
|
||
/* pop_cheats/speed применит pop_settings_apply после pop_sfx_init. */
|
||
pop_guard_reset();
|
||
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
||
printf("kid_data.bin load failed\n");
|
||
return -1;
|
||
}
|
||
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
||
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
|
||
pop_settings_apply(1); /* CFG -> speed/cheats/CBL */
|
||
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||
puts("kid atlas load failed");
|
||
return -1;
|
||
}
|
||
if (pop_level_load_num(FIRST_LEVEL) != 0) { /* сырой уровень в EMM-страницу */
|
||
puts("level load failed");
|
||
return -1;
|
||
}
|
||
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) */
|
||
|
||
/* FONT.ATL нужен palette snapshot/dim; загрузка идёт ещё на чёрной
|
||
* палитре и поэтому не открывает игроку старое содержимое VRAM. */
|
||
if (pop_menu_init() != 0) {
|
||
puts("menu font atlas load failed");
|
||
return -1;
|
||
}
|
||
/* kid.pal + динамические slots tileset/Shadow — один логический
|
||
* источник. pop_pal_game_load делает снимок ПОСЛЕ всех доплат. */
|
||
pop_pal_game_load();
|
||
/* Сохраняем ИМЕННО игровую палитру: после стартового title/intro она
|
||
* понадобится как источник обычного fade-in. Для build-info экран
|
||
* остаётся штатный яркий игровой фон. */
|
||
if (!pop_settings.show_build_info) {
|
||
pop_pal_apply(4);
|
||
}
|
||
|
||
/* Стартовая комната до title НЕ НУЖНА: LEVEL_LOAD всё равно заново
|
||
* выполняет pop_level_switch. Если нарисовать её сейчас в обе страницы,
|
||
* она становится сохранённой VRAM и может мелькнуть перед первым title
|
||
* при следующем запуске приложения. Единственное исключение — явно
|
||
* включённый build-info: его фон по контракту является игровой сценой. */
|
||
if (pop_settings.show_build_info) {
|
||
pop_start_level();
|
||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||
gfx_set_draw_page(0);
|
||
pop_char_draw(POP_CH_KID);
|
||
pop_char_fore(POP_CH_KID);
|
||
}
|
||
/* Звук включаем ПОСЛЕДНИМ действием инициализации — когда все атласы,
|
||
* уровень и палитры уже прочитаны. Раньше нельзя: ESTEX уходит в диск
|
||
* надолго, насос не успевает долить блок, и на слух это скрежет
|
||
* (pop_sfx.h). Парная к pop_sfx_pause в pop_level_switch. */
|
||
pop_sfx_start();
|
||
return 0;
|
||
}
|
||
|
||
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81): цикл
|
||
* уровня кончается, когда next_level разошёлся с current_level (END_LEVEL
|
||
* после seq_70 в двери уровня либо чит Shift+L), и play_level грузит
|
||
* следующий. Возврат -1 — фатально (не удалось сменить набор фона). */
|
||
int pop_level_switch(void) __banked
|
||
{
|
||
uint8_t old_page;
|
||
gfx_rect_t full;
|
||
|
||
/* Граница FG0: повреждённый snapshot или старый внешний вызов не должен
|
||
* даже пытаться открыть res2015.bin. Финальный маршрут 14 -> ENDING
|
||
* добавит автомат приложения (FG11); пока просто оставляем уровень. */
|
||
if (pop_next_level > POP_LEVEL_LAST) {
|
||
pop_next_level = pop_current_level;
|
||
return 0;
|
||
}
|
||
|
||
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81):
|
||
* цикл уровня кончается, когда next_level разошёлся с
|
||
* current_level (END_LEVEL после seq_70 в двери уровня либо чит
|
||
* Shift+L), и play_level грузит следующий.
|
||
*
|
||
* HP переносится: hitp_beg_lev = hitp_max ДО загрузки — так
|
||
* поднятая на уровне «жизнь» (большая склянка) переезжает
|
||
* дальше, а урон — нет (do_startpos дольёт до потолка). */
|
||
hitp_beg_lev = hitp_max;
|
||
pop_checkpoint = 0; /* уровень пройден (seg003:413) */
|
||
pop_sfx_pause(); /* загрузка уровня — ESTEX надолго, звук глушим */
|
||
if (pop_level_load_num(pop_next_level) != 0) {
|
||
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
||
} else {
|
||
/* С этого места видим только индекс 0. Это одновременно
|
||
* скрывает дисковую подготовку и даёт безопасный экран,
|
||
* пока loaders физически меняют отдельные диапазоны palette. */
|
||
pop_pal_black();
|
||
old_page = gfx_get_visible_page();
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
/* Обнуляем ОБЕ страницы, а не только software-visible:
|
||
* аппаратный SCREEN_SWITCH и сохранённое BGI-состояние не
|
||
* должны оставлять старые sprites на соседней странице. */
|
||
gfx_set_draw_page(0);
|
||
pop_screen_fill_game();
|
||
gfx_set_draw_page(1);
|
||
pop_screen_fill_game();
|
||
pop_wait_edge();
|
||
/* Набор спрайтов соперника у каждого уровня СВОЙ
|
||
* (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и
|
||
* кадров меньше, чем у стража. Без перезагрузки соперник
|
||
* рисуется чужим атласом: часть индексов уходит за его
|
||
* пределы, и персонаж просто не виден. */
|
||
pop_guard_free();
|
||
pop_guard_load();
|
||
/* guard loader применяет свои цветовые слоты физически;
|
||
* пока остальной уровень читается, экран обязан быть чёрным. */
|
||
pop_pal_black();
|
||
/* Зеркальные копии атласов сделаны из СТАРЫХ страниц —
|
||
* после перезагрузки спрайтов они мусор (pop_vflip.h). */
|
||
pop_vflip_reset();
|
||
/* Тайлсет: подземелье <-> дворец (порт tbl_envir_ki,
|
||
* seg000:1108). Внутри — no-op, если тип не менялся;
|
||
* при смене выгружает старые атласы и правит 32 записи
|
||
* палитры. Только на границе уровня, не в кадре. */
|
||
if (pop_bg_load(pop_level_type()) != 0) {
|
||
puts("bg set load failed");
|
||
return -1;
|
||
}
|
||
/* Смена tileset тоже пишет env/wall slots. Не открывать их
|
||
* до готовности обеих страниц нового уровня. */
|
||
pop_pal_black();
|
||
/* Всё состояние прошлого уровня сносит сам pop_start_level:
|
||
* pop_trob_reset обнуляет room_seen (модификаторы комнат
|
||
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
||
* из неё же, loose чистит enter_room. */
|
||
level_prepare = 1;
|
||
pop_start_level();
|
||
level_prepare = 0;
|
||
/* pop_bg_load мог заменить только env/wall, а до этого
|
||
* экран жил после fade на чёрной палитре заставки. Полный
|
||
* game source обязателен даже для dungeon->dungeon: иначе
|
||
* fade_in восстановит старый (либо уже чёрный) snapshot.
|
||
* Яркость caller выберет сам: gameplay может показать кадр
|
||
* сразу, lifecycle title/demo — сначала apply(4)+fade_in. */
|
||
/* Уровень с зельем инверсии — заранее поднять зеркальный
|
||
* набор персонажей (pop_vflip.h): 34 страницы читаются ~1.5 с,
|
||
* и делать это после открытия палитры либо в момент глотка
|
||
* нельзя. Звать можно откуда угодно — функция идемпотентна. */
|
||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||
/* Последнее действие подготовки: собрать полный logical
|
||
* source новой палитры. Пока он собирается физическими
|
||
* записями, видимая old_page заполнена нулём. */
|
||
pop_pal_level_load(1);
|
||
/* Атомарный commit на границе растра: впервые показать
|
||
* готовую комнату уже с окончательной палитрой уровня.
|
||
* Вторую страницу синхронизировать аппаратной копией. */
|
||
pop_wait_edge();
|
||
gfx_set_visible_page(level_prepared_page);
|
||
gfx_set_draw_page(old_page);
|
||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
/* Уровень готов и показан — заявить «LEVEL N» (порт
|
||
* show_level из draw_level_first, seg003:222). */
|
||
pop_status_level();
|
||
}
|
||
/* CBL намеренно НЕ открываем здесь. После загрузки caller ещё
|
||
* может делать BIOS fade/palette либо восстанавливать QuickSave;
|
||
* открытый буфер за это время успевает повторить старую половину
|
||
* и даёт скрежет. Caller запускает звук последним действием. */
|
||
return 0;
|
||
}
|
||
|
||
/* Общий холодный маршрут title/intro/credits/demo -> настоящая игра.
|
||
* Держим его в банке 8: это разовый lifecycle-код, а не часть кадрового
|
||
* пути, и перенос возвращает резиденту W1 место под стек/heap. */
|
||
int pop_new_game_load(void) __banked
|
||
{
|
||
/* Клавиша, которой пропустили заставку/demo, не должна стать первым
|
||
* движением Кида или тут же открыть pause menu на уровне. */
|
||
while (kbd_raw_any_down()) {
|
||
kbd_raw_sync();
|
||
gfx_wait_vsync();
|
||
}
|
||
|
||
pop_kid_hp_newgame();
|
||
hitp_max = POP_START_HITP;
|
||
pop_timer_new_game();
|
||
pop_checkpoint = 0;
|
||
pop_next_level = FIRST_LEVEL;
|
||
|
||
/* Title мог завершиться на яркой title-палитре. Сначала погасить её,
|
||
* затем восстановить полный game snapshot; level_switch собирает обе
|
||
* страницы уже невидимо, и первый игровой кадр проявляется. */
|
||
pop_pal_apply(4);
|
||
gfx_set_draw_page(0);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_screen_fill_game();
|
||
gfx_set_draw_page(1);
|
||
pop_screen_fill_game();
|
||
gfx_set_visible_page(1);
|
||
pop_pal_game_load();
|
||
pop_pal_apply(4);
|
||
if (pop_level_switch() != 0) return -1;
|
||
/* Первый уровень появляется сразу, без fade, но только после того как
|
||
* обе страницы и его собственная палитра полностью готовы. */
|
||
pop_pal_apply(0);
|
||
(void)pop_sfx_start();
|
||
return 0;
|
||
}
|
||
|
||
void pop_shutdown(void) __banked
|
||
{
|
||
gfx_set_idle_hook(0); /* снять хук ДО закрытия raw-канала */
|
||
/* ЗВУК ГАСИМ ПЕРВЫМ, до closegraph и освобождения страниц. Дальше идут
|
||
* ESTEX-вызовы (mem_free_block на каждый атлас), а они уходят надолго —
|
||
* насос не успевает долить очередной блок, и железо доигрывает по кругу
|
||
* несвежую половину буфера. Замер записи MAME: на выходе по ESC ровно
|
||
* 11 мс шума на полной громкости, то есть ОДИН пропущенный блок (128
|
||
* сэмплов = 11,7 мс). Та же причина, по которой звук глушится на время
|
||
* загрузки уровня (pop_sfx.h). */
|
||
pop_sfx_close();
|
||
pop_ctrl_close();
|
||
closegraph();
|
||
pop_bg_free();
|
||
pop_kid_free();
|
||
pop_shadow_free();
|
||
pop_menu_free();
|
||
pop_guard_free();
|
||
pop_kid_data_free();
|
||
pop_level_free();
|
||
}
|