From 70c1668e9f2225901e9a8bd68a8559b1c31f4675 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 12 Aug 2026 17:22:05 +0300 Subject: [PATCH] =?UTF-8?q?ENTER-ROOM-FAST=20+=20=D0=B2=D1=8B=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BA=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D1=81=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=BD=D0=BA=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Вход в комнату: одна отрисовка в скрытую страницу, показ флипом, вторая страница — gfx_copy_page(DIRECT) вместо повторной отрисовки. Процесс рисования тайлов больше не виден, обе страницы синхронны (два снимка подряд идентичны). Видимую страницу главный цикл теперь перечитывает у железа в начале кадра: её меняют и вход в комнату, и переворот — оба из банка. MEM-COLD2 п.2: guard_over_kid с хелперами (objtile_at_char/char_x_left_of/ tile_div_mod) — в банк 8. Куча 1005 -> 1574 Б, цена — один трамплин на кадр. tests-host 6/6, size-check OK, в MAME проверены старт уровня 9, переходы между комнатами и отрисовка стража. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/l9_invert_plan.md | 15 ++- applications/PoP/roomtest/TASKS_OPEN.md | 5 + applications/PoP/roomtest/roomtest.c | 115 ++++++---------------- applications/PoP/roomtest/roomtest_cold.c | 85 ++++++++++++++++ applications/PoP/roomtest/roomtest_cold.h | 6 ++ 5 files changed, 138 insertions(+), 88 deletions(-) diff --git a/applications/PoP/docs/l9_invert_plan.md b/applications/PoP/docs/l9_invert_plan.md index d9d53fb..481fe1f 100644 --- a/applications/PoP/docs/l9_invert_plan.md +++ b/applications/PoP/docs/l9_invert_plan.md @@ -297,7 +297,20 @@ void gfx_copy_rect(uint8_t src_page, int sx, int sy, --- -# Часть III — побочная задача ENTER-ROOM-FAST +# Часть III — ENTER-ROOM-FAST — СДЕЛАНО 2026-08-12 + +Комната теперь рисуется ОДИН раз — в скрытую страницу, — тут же +показывается флипом, а вторая страница получает её `gfx_copy_page(DIRECT)`. +Процесс рисования тайлов больше не виден: игрок получает готовый кадр. +Проверено в MAME: старт уровня и чит-переход между комнатами, два +последовательных снимка идентичны (обе страницы синхронны, мерцания нет). + +Побочно понадобилось: видимую страницу главный цикл больше не ведёт сам, а +перечитывает у железа в начале кадра (`front = gfx_get_visible_page()`) — +её меняют и вход в комнату, и переворот, причём оба из банка, где локальные +переменные `main` недоступны. + +# Часть III (исходное описание) Идея пользователя, самостоятельная ценность (сейчас при входе в комнату видно, как рисуются тайлы): рисовать комнату ОДИН раз в скрытую страницу, показать её diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index bc82caf..f7b53c7 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -158,6 +158,11 @@ fore-слоя, и клип (`clip_char`), и брызги; полоса HP и л ### MEM-COLD2. Второй шаг разгрузки резидента +> **Пункт 2 сделан 2026-08-12**: `guard_over_kid` с хелперами +> (`objtile_at_char` / `char_x_left_of` / `tile_div_mod`) уехал в банк 8 — +> куча 1005 → **1574 Б**. Зовётся раз в кадр, то есть цена — один трамплин. +> Остались пункты 1 (`enter_room_side`) и 3 (блок редроя шва). + После [MEM-COLD1](TASKS_CLOSED.md#mem-cold1) куча 1707 Б. Когда упрётся снова, в банк 8 переезжают следующие кандидаты (в порядке выгоды): diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index db7a225..d678ab1 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -9,6 +9,7 @@ #include #include #include +#include /* gfx_copy_page — вход в комнату и переворот */ #include "pop_bg.h" #include "pop_kid.h" #include "pop_ctrl.h" @@ -145,89 +146,6 @@ static void update_kid_render_dx(void) pop_char_set_render_dx(POP_CH_KID, dx); } -/* ---- Порядок «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; -} - -/* 1 = страж рисуется ПОСЛЕ Кида (поверх). */ -static uint8_t guard_over_kid(void) -{ - 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); -} - /* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый * draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта * реально изменился. Пока Кид держит кнопку и гейт открыт — modifier стабилен @@ -379,11 +297,31 @@ static void enter_room_side(uint8_t room, uint8_t side) rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; } } seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */ - for (pg = 0; pg < 2; pg++) { + /* 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 разведёт) */ @@ -584,7 +522,11 @@ int main(void) } #endif - /* Рисуем в СКРЫТУЮ страницу (при dbuf); в однобуфере — в 0. */ + /* Рисуем в СКРЫТУЮ страницу (при dbuf); в однобуфере — в 0. + * Видимую перечитываем У ЖЕЛЕЗА, а не ведём свой счёт: её меняют и + * вход в комнату (ENTER-ROOM-FAST), и переворот экрана + * (pop_flip_screen), причём оба — из банка, где front недоступен. */ + front = gfx_get_visible_page(); back = dbuf ? (uint8_t)(front ^ 1) : 0; gfx_set_draw_page(back); @@ -595,8 +537,7 @@ int main(void) if (pop_upside_dirty) { pop_upside_dirty = 0; pop_flip_screen(); - front = gfx_get_visible_page(); - back = dbuf ? (uint8_t)(front ^ 1) : 0; + back = dbuf ? (uint8_t)(gfx_get_visible_page() ^ 1) : 0; gfx_set_draw_page(back); } pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */ diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index 315e33d..602c307 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -255,6 +255,91 @@ void pop_dbg_room_label(uint8_t room) __banked room_label_redraw--; } +/* ---- Порядок «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; +} + +/* 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). Оригинал diff --git a/applications/PoP/roomtest/roomtest_cold.h b/applications/PoP/roomtest/roomtest_cold.h index cadcb65..4fbefe8 100644 --- a/applications/PoP/roomtest/roomtest_cold.h +++ b/applications/PoP/roomtest/roomtest_cold.h @@ -50,6 +50,12 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked; * звать можно каждый кадр. */ void pop_dbg_room_label(uint8_t room) __banked; +/* Кто рисуется ПОЗЖЕ — Кид или соперник (порядок обхода тайлов оригинала, + * set_objtile_at_char + sort_curr_objs). 1 = соперник поверх. Зовётся раз + * в кадр, поэтому живёт в банке: трамплин на кадр дешевле полукилобайта + * резидента. */ +uint8_t guard_over_kid(void) __banked; + /* Перевернуть уже нарисованное (зелье инверсии, уровень 9): две копии * акселератором + инвалидация слотов персонажей и полосы HP. Звать РАЗ на * переключение, по флагу pop_upside_dirty. */