Мышь уровня 8 + холодная половина главного цикла в банк 8
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь, пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) — решётка (0,3) открывается — и убегает. Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты (images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама. - guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в autocontrol_opponent / play_guard / check_can_guard_see_kid; - pop_leveldoor_open стал word, как в оригинале (в него же считается задержка — байт переполнился бы через 255 кадров); - leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5); - полосы HP у мыши нет (seg000:1159); - tests-host/t_mouse: 17 проверок, единственный набор с guards.c. Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level, find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в roomtest_cold.c (--bank 8, n_banks = 8). _CODE 25653 -> 24825, куча -> 1707 Б; кадровый путь не тронут. Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась, нажала кнопку, решётка поехала вверх, мышь ушла). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "pop_cdraw.h" /* общая отрисовка персонажей (слоты Кид/соперник) */
|
||||
#include "pop_cheat.h"
|
||||
#include "pop_tune.h"
|
||||
#include "roomtest_cold.h" /* холодная половина цикла — в банке */
|
||||
|
||||
__sfr __at (0xFE) io_border;
|
||||
|
||||
@@ -54,7 +55,9 @@ __sfr __at (0xFE) io_border;
|
||||
* оверлеи, кладка), 3 = pop_map (коллизия/физика), 4 = pop_cdraw
|
||||
* (отрисовка персонажей + полоса HP), 5 = pop_ctrl (диспетчер управления),
|
||||
* 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина
|
||||
* фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты).
|
||||
* фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты),
|
||||
* 8 = roomtest_cold (ХОЛОДНАЯ половина главного цикла: старт уровня и
|
||||
* отладочная навигация — см. roomtest_cold.h).
|
||||
* Отрисовка персонажей и холодная половина фона вынесены из банка 2
|
||||
* отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть
|
||||
* куда — тайлы поздних уровней, чомперы, зеркало.
|
||||
@@ -64,7 +67,7 @@ __sfr __at (0xFE) io_border;
|
||||
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||
const uint8_t n_banks = 7;
|
||||
const uint8_t n_banks = 8;
|
||||
|
||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||
uint8_t pop_cheats;
|
||||
@@ -119,13 +122,14 @@ static void kbd_idle(void)
|
||||
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||
static uint8_t room_fg[30], room_bg[30];
|
||||
uint8_t room_fg[30];
|
||||
static uint8_t room_bg[30];
|
||||
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
||||
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
||||
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
||||
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
||||
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||
static uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||
uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||
|
||||
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||
@@ -258,10 +262,11 @@ static uint8_t seam_sig[3]; /* последняя нарисованная ope
|
||||
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||
|
||||
/* Отладочный лейбл номера комнаты — рисуем только при смене (outtextxy тяжёлая):
|
||||
* sig = последняя показанная комната, redraw = остаток кадров (2 = обе страницы). */
|
||||
static uint8_t room_label_sig = 0xFF;
|
||||
static uint8_t room_label_redraw;
|
||||
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
||||
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
||||
* стартовую позицию. Сбрасывается началом игры и прохождением уровня —
|
||||
* ровно как в оригинале (seg003:37 и seg003:413). */
|
||||
uint8_t pop_checkpoint;
|
||||
|
||||
/* Изменения тайлов (упавшая плита → пусто, щебень на месте приземления
|
||||
* куска, поднятый предмет → пол) пишутся ПРЯМО В УРОВЕНЬ —
|
||||
@@ -387,7 +392,7 @@ static void enter_room_side(uint8_t room, uint8_t side)
|
||||
|
||||
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
||||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||||
static void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||
void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||
|
||||
/* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos
|
||||
* (seg003:028A) — поставить Кида в стартовую позицию УРОВНЯ. Один вход и
|
||||
@@ -414,109 +419,6 @@ static void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
|
||||
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
|
||||
|
||||
/* 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) */
|
||||
}
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
||||
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
|
||||
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
|
||||
uint8_t start_room = pop_level_start_room();
|
||||
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): в
|
||||
* исходное возвращаются И модификаторы (пики/ворота), И сами тайлы —
|
||||
* разбитые плиты целы, выпитые зелья на месте (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 */
|
||||
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), иначе шов кадр показывал бы
|
||||
* ещё не тронутое состояние решётки. */
|
||||
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]);
|
||||
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);
|
||||
/* Спецсобытие «вход падением» (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 */
|
||||
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
|
||||
* (=2) — начиная со второго уровня меч у Кида уже есть. */
|
||||
pop_have_sword = (uint8_t)(pop_current_level >= 2);
|
||||
pop_kid_hp_reset();
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
||||
@@ -663,89 +565,13 @@ int main(void)
|
||||
#ifdef ROOMNAV
|
||||
/* Обход комнат по номеру: '+' (0x79 numpad / 0x55 '=') — вперёд,
|
||||
* '-' (0x7B / 0x4E) — назад. Edge по nav_prev: одно нажатие = одна
|
||||
* комната.
|
||||
*
|
||||
* Здесь БЫЛ pop_trob_reset() перед enter_room — «чтобы каждая комната
|
||||
* смотрелась в исходном состоянии». Убран: reset обнуляет room_seen,
|
||||
* и модификаторы перечитываются из уровня заново, то есть чит
|
||||
* ОТМАТЫВАЕТ МИР — открытые/закрытые ворота, выдвинутые пики, нажатые
|
||||
* кнопки. Пока ворота с bg=1 ошибочно стартовали закрытыми, откат был
|
||||
* не виден; после порта load_alter_mod он выглядит как баг «ворота
|
||||
* снова открылись» (уровень 1, решётка комнаты 5 (0,9), которую
|
||||
* закрывает стартовый триггер do_startpos). Навигация обязана только
|
||||
* телепортировать; исходное состояние даёт перезапуск уровня. */
|
||||
* комната. Сама навигация — в банке (roomtest_cold.c): она нужна
|
||||
* только по клавише, а место в резиденте дефицитно. */
|
||||
{
|
||||
uint8_t nav = (uint8_t)((kbd_raw_down(0x79) || kbd_raw_down(0x55)) |
|
||||
((kbd_raw_down(0x7B) || kbd_raw_down(0x4E)) << 1));
|
||||
if (nav && !nav_prev) {
|
||||
uint8_t r = cur_room, 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(); dead_frames = 0; }
|
||||
r = (nav & 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();
|
||||
if (nav && !nav_prev && pop_dbg_roomnav((uint8_t)(nav & 1 ? 1 : 2)))
|
||||
dead_frames = 0;
|
||||
/* ВЫХОД ИЗ БОЯ (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;
|
||||
}
|
||||
nav_prev = nav;
|
||||
}
|
||||
#endif
|
||||
@@ -755,6 +581,7 @@ int main(void)
|
||||
gfx_set_draw_page(back);
|
||||
|
||||
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
|
||||
pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */
|
||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||
/* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы —
|
||||
@@ -1045,26 +872,7 @@ int main(void)
|
||||
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
|
||||
* а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */
|
||||
#ifdef ROOMNAV
|
||||
{
|
||||
uint8_t r = cur_room, prev = room_label_sig;
|
||||
room_label_sig = r; /* СНАЧАЛА сохранить (sdcc_z80_cmp_store_a_bug) */
|
||||
if (r != prev) room_label_redraw = 2;
|
||||
if (room_label_redraw) {
|
||||
/* Номер ПАЛОЧКАМИ, а не outtextxy: текст тянет системный
|
||||
* знакогенератор (_gfx_font_buf, 2 КБ статики в W2) и сажает
|
||||
* кучу с 3867 до 576 Б. Слева десятки, справа единицы:
|
||||
* || |||| = 24. bar/setfillstyle уже используются. */
|
||||
uint8_t i;
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(100, 0, 175, 5);
|
||||
/* 0x57, а НЕ WHITE: палитра игровая, запись 15 в ней чёрная
|
||||
* (kid.pal: 0x0F = 0,0,0; чистый белый = 0x57). */
|
||||
setfillstyle(SOLID_FILL, 0x57);
|
||||
for (i = 0; i < (uint8_t)(r / 10); i++) bar(100 + i * 5, 0, 101 + i * 5, 5);
|
||||
for (i = 0; i < (uint8_t)(r % 10); i++) bar(130 + i * 5, 0, 131 + i * 5, 5);
|
||||
room_label_redraw--;
|
||||
}
|
||||
}
|
||||
pop_dbg_room_label(cur_room); /* номер комнаты палочками — в банке */
|
||||
#endif
|
||||
|
||||
gfx_wait_vsync();
|
||||
|
||||
Reference in New Issue
Block a user