L9-INVERT: зеркальный слой фона + вход в комнату в банк 8
II.3: pop_upside проведён в единственную точку тайлового слоя (pop_blit_b / blit_b_clip): позиция через FLIP_TOP, блит — vflip-двойник. Этим зеркалятся полная отрисовка комнаты, точечные редрои, trob-анимации, fore-слой и кладка. В клипованном пути обрезка сверху экрана съедает нижние строки источника, поэтому кусок пересчитывается как sy' = h - sy - dh. MEM-COLD2 п.1: enter_room_side/enter_room — в банк 8 (куча 450 -> 1231 Б). Рабочие массивы комнаты остались в _DATA, в банк уехал только код. Проверено в MAME (уровень 9, чит U): комната перевёрнута целиком, пол вверху, дверь и кладка вверх ногами. tests-host 6/6, size-check OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -243,7 +243,29 @@ void gfx_copy_rect(uint8_t src_page, int sx, int sy,
|
||||
вероятный класс багов здесь). Решение: зеркалит ВЫЗЫВАЮЩИЙ, у примитивов
|
||||
семантика не меняется.
|
||||
|
||||
## II.3 Фон
|
||||
## II.3 Фон — СДЕЛАНО 2026-08-12
|
||||
|
||||
Флаг проведён в ЕДИНСТВЕННУЮ точку — `pop_blit_b`/`blit_b_clip`
|
||||
(`pop_tile.c`): позиция пересчитывается макросом `FLIP_TOP` (он один на весь
|
||||
слой — дублировать нельзя, иначе двойной переворот), а блит выбирает
|
||||
vflip-двойник. Тем самым зеркалятся и полная отрисовка комнаты, и точечные
|
||||
редрои, и анимации trob, и fore-слой, и кладка — все они ходят через эту
|
||||
функцию.
|
||||
|
||||
Тонкость клипованного пути: при зеркале обрезка СВЕРХУ экрана съедает
|
||||
НИЖНИЕ строки источника, поэтому кусок пересчитывается как
|
||||
`sy' = h - sy - dh`.
|
||||
|
||||
Проверено в MAME (уровень 9, чит U): комната перевёрнута целиком — пол
|
||||
вверху, дверь уровня и кладка вверх ногами. Персонажи пока обычные (II.4).
|
||||
|
||||
Цена: три функции libbgi ушли в резидент, куча 1574 → 450 Б, поэтому тем же
|
||||
заходом сделан **MEM-COLD2 п.1** — `enter_room_side`/`enter_room` уехали в
|
||||
банк 8 (куча вернулась к **1231 Б**, банк занят на 14.6%). Рабочие массивы
|
||||
комнаты остались в `_DATA` и видны обеим половинам; `enter_room` обязана быть
|
||||
`__banked` — её зовёт главный цикл.
|
||||
|
||||
### Контракт (исходное описание)
|
||||
|
||||
Одна точка: `pop_tile.c` (блит куска атласа) выбирает vflip-двойник по
|
||||
`pop_upside` и пересчитывает `y`. Тогда автоматически попадают:
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
#include <graphics.h>
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_bg.h" /* POP_YOFF */
|
||||
#include "pop_bg.h" /* POP_YOFF, POP_PLAYFIELD_H */
|
||||
#include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */
|
||||
#include "pop_cdraw.h" /* POP_CD_TOUCH — фон тронут, персонажей перерисовать */
|
||||
#include "pop_tile.h"
|
||||
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
|
||||
@@ -262,6 +263,12 @@ void pop_cd_clear(uint8_t p)
|
||||
* платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46).
|
||||
* А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья,
|
||||
* перерисовка тайлов — идёт с pop_t_fclip_on == 0. */
|
||||
/* Экранный y при ПЕРЕВОРОТЕ (зелье инверсии): поле отражается внутри себя,
|
||||
* борта не трогаются. Одна формула на весь слой фона — если её продублируют
|
||||
* где-то ещё, получим двойной переворот (см. l9_invert_plan.md §II.2:
|
||||
* зеркалит ТОЛЬКО вызывающий, у примитивов семантика не меняется). */
|
||||
#define FLIP_TOP(top, h) (2 * POP_YOFF + POP_PLAYFIELD_H - (top) - (int)(h))
|
||||
|
||||
static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
|
||||
{
|
||||
int dx = x, dy = top, dw = w, dh = h, sx = 0, sy = 0;
|
||||
@@ -275,6 +282,20 @@ static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h)
|
||||
if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
|
||||
}
|
||||
if (dw <= 0 || dh <= 0) return;
|
||||
/* ПЕРЕВОРОТ: экранная строка (dy+i) берёт источник (h-1-i), поэтому
|
||||
* обрезка СВЕРХУ экрана съедает НИЖНИЕ строки источника. В терминах
|
||||
* куска источника это sy' = h - sy - dh (при обрезке только сверху даёт
|
||||
* 0, при обрезке только снизу — верхний край). */
|
||||
if (pop_upside) {
|
||||
int fsy = h - sy - dh;
|
||||
if (fsy < 0) fsy = 0;
|
||||
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||
dx + dw <= 320 && dy + dh <= 256 && w < 256)
|
||||
gfx_blit_part_noclip_vflip(dx, dy, img, (uint8_t)sx, (uint8_t)fsy,
|
||||
(uint8_t)dw, (uint8_t)dh);
|
||||
else
|
||||
gfx_blit_part_vflip(dx, dy, img, sx, fsy, dw, dh);
|
||||
} else
|
||||
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
|
||||
if (sx == 0 && sy == 0 && dw == w && dh == h)
|
||||
@@ -313,6 +334,10 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
|
||||
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в
|
||||
* экранных координатах результата. */
|
||||
if (pop_upside) top = FLIP_TOP(top, h);
|
||||
if (w && h) {
|
||||
if (!pop_t_fclip_on && !pop_t_clip_top) {
|
||||
/* БЫСТРЫЙ путь: клипа нет вовсе. Так идут все блиты фона вне
|
||||
@@ -321,10 +346,13 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
* платит за клип/16-бит/split, а не за пиксели). W3-скобку
|
||||
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
|
||||
if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
||||
x + (int)w <= 320 && top + (int)h <= 256)
|
||||
gfx_blit_noclip(x, top, img);
|
||||
else
|
||||
gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
||||
x + (int)w <= 320 && top + (int)h <= 256) {
|
||||
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
|
||||
else gfx_blit_noclip(x, top, img);
|
||||
} else {
|
||||
if (pop_upside) gfx_blit_part_vflip(x, top, img, 0, 0, (int)w, (int)h);
|
||||
else gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
||||
}
|
||||
pop_cd_touch(x, top, (int)w, (int)h);
|
||||
} else {
|
||||
blit_b_clip(img, x, top, (int)w, (int)h);
|
||||
|
||||
@@ -124,14 +124,14 @@ static void kbd_idle(void)
|
||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||
uint8_t room_fg[30];
|
||||
static uint8_t room_bg[30];
|
||||
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 верхнего соседа (полоса кладки у потолка) */
|
||||
uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
||||
uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||
uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
||||
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||
uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
|
||||
|
||||
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
||||
@@ -167,7 +167,7 @@ static void update_kid_render_dx(void)
|
||||
* Видимой её делает draw_tile: нажатый opener рисуется как ПОЛ (seg008
|
||||
* get_tile_to_draw), а у пола есть правая грань, у кнопки — нет.
|
||||
* Поэтому в сигнатуру ряда подмешиваем бит «кнопка нажата». */
|
||||
static uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r)
|
||||
uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r)
|
||||
{
|
||||
uint8_t code = (uint8_t)(lfg[r] & 0x1F);
|
||||
uint8_t mod = m[9 + r * 10];
|
||||
@@ -176,9 +176,9 @@ static uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r)
|
||||
return mod;
|
||||
}
|
||||
|
||||
static uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */
|
||||
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||
uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */
|
||||
uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||
uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||
|
||||
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
||||
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
||||
@@ -214,149 +214,6 @@ static void flash_bg(uint8_t r, uint8_t g, uint8_t b)
|
||||
gfx_pal_set(1, 0, r, g, b);
|
||||
}
|
||||
|
||||
/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ
|
||||
* страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет
|
||||
* активной страницу 1 — вызывающий восстанавливает нужную draw-страницу. */
|
||||
/* side — куда ушёл КИД (0 left, 1 right, 2 up, 3 down); 0xFF = «не переход
|
||||
* за Кидом» (старт уровня, респавн, чит-навигация). От него зависит ровно
|
||||
* одно: пойдёт ли страж следом (pop_guard_follow, порт exit_room+
|
||||
* follow_guard) или останется в прежней комнате. */
|
||||
static void enter_room_side(uint8_t room, uint8_t side)
|
||||
{
|
||||
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(); /* запомнить стража уходящей комнаты */
|
||||
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_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */
|
||||
/* 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);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
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 = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
|
||||
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
|
||||
}
|
||||
|
||||
/* Вход в комнату БЕЗ перехода за Кидом: старт уровня, респавн, чит-обход.
|
||||
* Страж такой смены не «видит» — он остаётся в своей комнате. */
|
||||
void enter_room(uint8_t room) { enter_room_side(room, 0xFF); }
|
||||
|
||||
/* 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 */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
||||
|
||||
@@ -255,6 +255,150 @@ void pop_dbg_room_label(uint8_t room) __banked
|
||||
room_label_redraw--;
|
||||
}
|
||||
|
||||
/* Загрузить комнату 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(); /* запомнить стража уходящей комнаты */
|
||||
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_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */
|
||||
/* 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);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
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 = 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); }
|
||||
|
||||
/* 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) -----------------------
|
||||
*
|
||||
* В оригинале никто не «поверх» по определению: и Кид, и страж попадают в
|
||||
|
||||
@@ -23,14 +23,30 @@
|
||||
|
||||
/* ---- то, что холодная половина берёт у главного цикла ---------------- */
|
||||
|
||||
extern uint8_t room_fg[30]; /* тайлы ОТРИСОВАННОЙ комнаты (fg & 0x1F) */
|
||||
/* Рабочие массивы комнаты: заполняет вход в комнату (банк), читают и он, и
|
||||
* главный цикл (шов, straddle, чит-навигация). Данные банковых модулей
|
||||
* линкуются в общий _DATA, поэтому обе половины видят их как обычные
|
||||
* глобалы — в банк уехал только КОД. */
|
||||
extern uint8_t room_fg[30], room_bg[30];
|
||||
extern uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
||||
extern uint8_t above_fg[10], above_mod[10];
|
||||
extern uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (straddle) */
|
||||
extern uint8_t seam_sig[3]; /* последняя нарисованная openness в шве */
|
||||
extern uint8_t seam_rows, seam_redraw;
|
||||
|
||||
/* Сигнатура ряда шва: openness ворот соседа либо «кнопка нажата». Живёт в
|
||||
* резиденте — её зовёт и кадровый путь главного цикла, и вход в комнату. */
|
||||
uint8_t seam_row_sig(const uint8_t *m, const uint8_t *lfg, uint8_t r);
|
||||
extern uint8_t cur_room; /* её номер (drawn_room) */
|
||||
extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */
|
||||
/* влево из комнаты 7 уровня 3 */
|
||||
|
||||
/* Вход в комнату БЕЗ перехода за Кидом (старт уровня, респавн, чит-обход):
|
||||
* страж такой смены не «видит» и остаётся в своей комнате. */
|
||||
void enter_room(uint8_t room);
|
||||
/* Вход в комнату. side — куда ушёл КИД (0 L, 1 R, 2 U, 3 D); 0xFF = «не
|
||||
* переход за Кидом» (старт уровня, респавн, чит-навигация): от этого
|
||||
* зависит, пойдёт ли страж следом. enter_room — обёртка с 0xFF.
|
||||
* Обе в банке 8: зовутся раз на комнату, а кода там на полтора килобайта. */
|
||||
void enter_room_side(uint8_t room, uint8_t side) __banked;
|
||||
void enter_room(uint8_t room) __banked;
|
||||
|
||||
/* ---- холодная половина ----------------------------------------------- */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user