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_tile.c` (блит куска атласа) выбирает vflip-двойник по
|
||||||
`pop_upside` и пересчитывает `y`. Тогда автоматически попадают:
|
`pop_upside` и пересчитывает `y`. Тогда автоматически попадают:
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
#include <graphics.h>
|
#include <graphics.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.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_cdraw.h" /* POP_CD_TOUCH — фон тронут, персонажей перерисовать */
|
||||||
#include "pop_tile.h"
|
#include "pop_tile.h"
|
||||||
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
|
#include "pop_level.h" /* pop_doorlink2 — подстановка нажатой кнопки */
|
||||||
@@ -262,6 +263,12 @@ void pop_cd_clear(uint8_t p)
|
|||||||
* платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46).
|
* платили и блиты БЕЗ клипа (`ld -n(ix)` = 19 тактов номинала, у нас ~46).
|
||||||
* А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья,
|
* А блитов без клипа большинство: весь фон вне fore-прохода — факелы, зелья,
|
||||||
* перерисовка тайлов — идёт с pop_t_fclip_on == 0. */
|
* перерисовка тайлов — идёт с 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)
|
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;
|
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 (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy;
|
||||||
}
|
}
|
||||||
if (dw <= 0 || dh <= 0) return;
|
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 &&
|
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
|
||||||
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
|
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
|
||||||
if (sx == 0 && sy == 0 && dw == w && dh == h)
|
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));
|
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||||
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
|
||||||
|
/* Переворот применяем ЗДЕСЬ, до клипа: дальше вся геометрия (окно
|
||||||
|
* fore-слоя, полоса у потолка, отсев по экрану) считается уже в
|
||||||
|
* экранных координатах результата. */
|
||||||
|
if (pop_upside) top = FLIP_TOP(top, h);
|
||||||
if (w && h) {
|
if (w && h) {
|
||||||
if (!pop_t_fclip_on && !pop_t_clip_top) {
|
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-скобку
|
* платит за клип/16-бит/split, а не за пиксели). W3-скобку
|
||||||
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
|
* ставит сама libbgi: из кода с --w3 её вызывать нельзя. */
|
||||||
if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
if (x >= 0 && top >= 0 && w < 256 && h < 256 &&
|
||||||
x + (int)w <= 320 && top + (int)h <= 256)
|
x + (int)w <= 320 && top + (int)h <= 256) {
|
||||||
gfx_blit_noclip(x, top, img);
|
if (pop_upside) gfx_blit_noclip_vflip(x, top, img);
|
||||||
else
|
else gfx_blit_noclip(x, top, img);
|
||||||
gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h);
|
} 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);
|
pop_cd_touch(x, top, (int)w, (int)h);
|
||||||
} else {
|
} else {
|
||||||
blit_b_clip(img, x, top, (int)w, (int)h);
|
blit_b_clip(img, x, top, (int)w, (int)h);
|
||||||
|
|||||||
@@ -124,14 +124,14 @@ static void kbd_idle(void)
|
|||||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||||
uint8_t room_fg[30];
|
uint8_t room_fg[30];
|
||||||
static uint8_t room_bg[30];
|
uint8_t room_bg[30];
|
||||||
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
/* lcol_fg/rcol_fg — ШЕСТЬ записей: [0..2] кромочная колонка соседа (col9/col0),
|
||||||
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
* [3..5] следующая за ней (col8/col1) для коллизии, когда Kid стоит В шве и
|
||||||
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
* смотрит наружу (колонка −2 / 11). См. pop_level.h. */
|
||||||
static uint8_t lcol_fg[6], lcol_bg[3], rcol_fg[6], rcol_bg[3], below_fg[11];
|
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 above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
|
||||||
uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
|
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
|
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
|
||||||
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
|
||||||
@@ -167,7 +167,7 @@ static void update_kid_render_dx(void)
|
|||||||
* Видимой её делает draw_tile: нажатый opener рисуется как ПОЛ (seg008
|
* Видимой её делает draw_tile: нажатый opener рисуется как ПОЛ (seg008
|
||||||
* get_tile_to_draw), а у пола есть правая грань, у кнопки — нет.
|
* 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 code = (uint8_t)(lfg[r] & 0x1F);
|
||||||
uint8_t mod = m[9 + r * 10];
|
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;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */
|
uint8_t seam_sig[3]; /* последняя нарисованная openness гейта в шве */
|
||||||
static uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
uint8_t seam_rows; /* маска рядов, ждущих редроя (переживает 2 кадра) */
|
||||||
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
|
||||||
|
|
||||||
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
/* checkpoint (seg000:270): взводится, когда Кид уходит ВЛЕВО в комнату 7
|
||||||
* уровня 3 (level3_set_chkp, seg002:0665); по нему do_startpos подменяет
|
* уровня 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);
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
/* Дабл-буфер: front — видимая страница, back — куда рисуем. dbuf=1
|
||||||
|
|||||||
@@ -255,6 +255,150 @@ void pop_dbg_room_label(uint8_t room) __banked
|
|||||||
room_label_redraw--;
|
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) -----------------------
|
/* ---- Порядок «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 cur_room; /* её номер (drawn_room) */
|
||||||
extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */
|
extern uint8_t pop_checkpoint; /* checkpoint (seg000:270) — ставит уход */
|
||||||
/* влево из комнаты 7 уровня 3 */
|
/* влево из комнаты 7 уровня 3 */
|
||||||
|
|
||||||
/* Вход в комнату БЕЗ перехода за Кидом (старт уровня, респавн, чит-обход):
|
/* Вход в комнату. side — куда ушёл КИД (0 L, 1 R, 2 U, 3 D); 0xFF = «не
|
||||||
* страж такой смены не «видит» и остаётся в своей комнате. */
|
* переход за Кидом» (старт уровня, респавн, чит-навигация): от этого
|
||||||
void enter_room(uint8_t room);
|
* зависит, пойдёт ли страж следом. 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