ENTER-ROOM-FAST + вынос порядка отрисовки в банк 8

Вход в комнату: одна отрисовка в скрытую страницу, показ флипом, вторая
страница — 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 17:22:05 +03:00
parent b1ef48bd16
commit 70c1668e9f
5 changed files with 138 additions and 88 deletions
+14 -1
View File
@@ -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 (исходное описание)
Идея пользователя, самостоятельная ценность (сейчас при входе в комнату видно, Идея пользователя, самостоятельная ценность (сейчас при входе в комнату видно,
как рисуются тайлы): рисовать комнату ОДИН раз в скрытую страницу, показать её как рисуются тайлы): рисовать комнату ОДИН раз в скрытую страницу, показать её
+5
View File
@@ -158,6 +158,11 @@ fore-слоя, и клип (`clip_char`), и брызги; полоса HP и л
<a id="mem-cold2"></a> <a id="mem-cold2"></a>
### MEM-COLD2. Второй шаг разгрузки резидента ### 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 Б. Когда упрётся После [MEM-COLD1](TASKS_CLOSED.md#mem-cold1) куча 1707 Б. Когда упрётся
снова, в банк 8 переезжают следующие кандидаты (в порядке выгоды): снова, в банк 8 переезжают следующие кандидаты (в порядке выгоды):
+28 -87
View File
@@ -9,6 +9,7 @@
#include <conio.h> #include <conio.h>
#include <stdio.h> #include <stdio.h>
#include <kbd_raw.h> #include <kbd_raw.h>
#include <sprite.h> /* gfx_copy_page — вход в комнату и переворот */
#include "pop_bg.h" #include "pop_bg.h"
#include "pop_kid.h" #include "pop_kid.h"
#include "pop_ctrl.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); 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): тяжёлый /* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый
* draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта * draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта
* реально изменился. Пока Кид держит кнопку и гейт открыт modifier стабилен * реально изменился. Пока Кид держит кнопку и гейт открыт 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]; } rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; }
} }
seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */ 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); gfx_set_draw_page(pg);
setfillstyle(SOLID_FILL, BLACK); setfillstyle(SOLID_FILL, BLACK);
bar(0, 0, 319, 255); bar(0, 0, 319, 255);
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg); 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; cur_room = room;
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */ kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
@@ -584,7 +522,11 @@ int main(void)
} }
#endif #endif
/* Рисуем в СКРЫТУЮ страницу (при dbuf); в однобуфере — в 0. */ /* Рисуем в СКРЫТУЮ страницу (при dbuf); в однобуфере — в 0.
* Видимую перечитываем У ЖЕЛЕЗА, а не ведём свой счёт: её меняют и
* вход в комнату (ENTER-ROOM-FAST), и переворот экрана
* (pop_flip_screen), причём оба из банка, где front недоступен. */
front = gfx_get_visible_page();
back = dbuf ? (uint8_t)(front ^ 1) : 0; back = dbuf ? (uint8_t)(front ^ 1) : 0;
gfx_set_draw_page(back); gfx_set_draw_page(back);
@@ -595,8 +537,7 @@ int main(void)
if (pop_upside_dirty) { if (pop_upside_dirty) {
pop_upside_dirty = 0; pop_upside_dirty = 0;
pop_flip_screen(); pop_flip_screen();
front = gfx_get_visible_page(); back = dbuf ? (uint8_t)(gfx_get_visible_page() ^ 1) : 0;
back = dbuf ? (uint8_t)(front ^ 1) : 0;
gfx_set_draw_page(back); gfx_set_draw_page(back);
} }
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */ pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
+85
View File
@@ -255,6 +255,91 @@ void pop_dbg_room_label(uint8_t room) __banked
room_label_redraw--; 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) --------- * /* ---- Переворот уже нарисованного (зелье инверсии, уровень 9) --------- *
* *
* Порт need_redraw_because_flipped (seg000:15E9 + 939/946). Оригинал * Порт need_redraw_because_flipped (seg000:15E9 + 939/946). Оригинал
@@ -50,6 +50,12 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked;
* звать можно каждый кадр. */ * звать можно каждый кадр. */
void pop_dbg_room_label(uint8_t room) __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): две копии /* Перевернуть уже нарисованное (зелье инверсии, уровень 9): две копии
* акселератором + инвалидация слотов персонажей и полосы HP. Звать РАЗ на * акселератором + инвалидация слотов персонажей и полосы HP. Звать РАЗ на
* переключение, по флагу pop_upside_dirty. */ * переключение, по флагу pop_upside_dirty. */