Пункт 0: отсев кладки по окну fore-клипа; закрыт BUG-CHEAT-FIGHT-1

Три шага пункта 0 из docs/perf_backlog.md.

1. Ранний выход из wall_pattern_palace по окну fore-клипа: узор целиком
   лежит в x [xh*8, xh*8+32), y [dmy-59, dby], и если окно его не задевает
   — возврат до первой заливки.  Замер: от входа в узор до конца всего
   прохода 6 055 тактов вместо ~60 000 на тайл.
2. Отсев КАЖДОГО декаля (wp_blit) по реальному габариту.  pop_blit_b тоже
   отсеивает до маппинга страницы, но по заведомо большему 64x64 — куски
   кладки высотой 3..12 px он пропускал и платил полный
   atlas_image + gfx_w0_map (~9 760 тактов на блит), чтобы там обнаружить,
   что рисовать нечего.  Размеры сняты из каталогов атласов и заданы верхней
   границей по группе.
3. То же для ПОДЗЕМЕЛЬЯ: ранний выход wall_pattern (габарит выше — левая
   марка уходит на dby+POP_YOFF-67) плюс wp_blit на RNDBLOCK, обоих
   разделителях и обеих марках.

Замер (уровень 4, комната 18, Кид сдвигается читом ] по пикселю, skip
выключен, шесть тайлов в fore-окне, три из них — дворцовая стена):
тайл стены ~12 700 вместо 60 000-74 000, fore-проход целиком 70 681 вместо
171 693, работа за кадр 419 839, период 3 растровых кадра вместо 4.
Уровень 1 (подземелье) проверен снимком — кладка, марки, разделители на
месте.

Остаток в проходе — пролог pop_char_fore 17 208 тактов (пункт 1 backlog'а).
Отдельно записано: на ШВЕ пролог разбухает до 134 730, причина не разобрана.

BUG-CHEAT-FIGHT-1 (заведён 2026-08-07) закрыт по своему же плану: ветка
ROOMNAV после kid_init/pop_kid_hp_reset гасит состояние схватки
(Kid.sword = SWORD_0_SHEATHED, holding_sword, offguard, guard_refrac).
Меч в инвентаре не теряется.  Это болезнь чита: в оригинале телепорта между
комнатами нет и в режим боя без соперника попасть нечем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 18:24:27 +03:00
parent 4718bff767
commit 08ac38ce3d
5 changed files with 175 additions and 55 deletions
+74 -11
View File
@@ -78,6 +78,10 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
* читает кэш раскладки кладки. */
#define prandom(maxv) pop_prandom(&pop_t_seed, (maxv))
/* Отсев кусков кладки по окну fore-клипа — тела ниже, у wpp_fill. */
static uint8_t wp_vis(int x, int w, int ytop, int ybot);
static void wp_blit(uint8_t id, int x, int ybot, uint8_t w, uint8_t h);
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
{
@@ -87,7 +91,8 @@ static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
int dby = 63 * row + 65;
if (dv > 3) lv2 = arg1 + 6;
else if (dv > 1) lv2 = arg2 + 6;
pop_wall_b(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2, dby - LPOS[dv]);
wp_blit(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2,
dby - LPOS[dv], 7, 10); /* pop_wall.atl: 16/17 = 7x10 */
}
static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
@@ -96,7 +101,8 @@ static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
int a = (dv < 2) ? 24 : arg1 - 3;
int xh = POP_COL_XH[col];
int dby = 63 * row + 65;
pop_wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]);
wp_blit(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a,
dby - RPOS[dv], 14, 5); /* pop_wall.atl: 14/15 = 14x5 */
}
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
@@ -290,6 +296,32 @@ static void wall_rnd_palace(int row, int col)
}
}
/* Прямоугольник [x, x+w) x [ytop, ybot] (ЭКРАННЫЕ) задевает окно fore-клипа?
* Клипа нет — задевает всегда. */
static uint8_t wp_vis(int x, int w, int ytop, int ybot)
{
if (!pop_t_fclip_on) return 1;
return (uint8_t)(x < pop_t_fclip_x1 && x + w > pop_t_fclip_x0 &&
ytop < pop_t_fclip_y1 && ybot >= pop_t_fclip_y0);
}
/* Декаль кладки: ybot — БЕЗ POP_YOFF (как ждёт pop_wall_b), w/h — реальный
* габарит куска. Размеры взяты из каталогов атласов (pal_wall.atl /
* pop_wall.atl) и заданы верхней границей по группе, поэтому промахнуться в
* меньшую сторону нельзя: хуже отсева не будет, лишнего не сотрём.
*
* Зачем: pop_blit_b тоже отсеивает до маппинга страницы, но по ЗАВЕДОМО
* большему габариту 64x64 — куски кладки высотой 3..12 px он пропускает и
* платит полный atlas_image + gfx_w0_map + чтение w/h (~9 760 тактов на
* блит), чтобы там обнаружить, что рисовать нечего. Пять таких блитов на
* тайл — 48 500 тактов в никуда (docs/perf_backlog.md, пункт 0 шаг 2). */
static void wp_blit(uint8_t id, int x, int ybot, uint8_t w, uint8_t h)
{
int yb = ybot + POP_YOFF;
if (!wp_vis(x, (int)w, yb - (int)h + 1, yb)) return;
pop_wall_b(id, x, ybot);
}
/* Заливка куска стены. x/ytop — уже ЭКРАННЫЕ (с POP_YOFF). В fore-проходе
* режем по окну клипа: без этого заливка легла бы поверх областей, которые
* в этом кадре никто не восстанавливает. */
@@ -319,6 +351,23 @@ static void wall_pattern_palace(int row, int col, int which_part)
int dbys = 63 * row + 65; /* база БЕЗ POP_YOFF — для блитов */
int dmys = dbys - 3;
int dby = dbys + POP_YOFF, dmy = dmys + POP_YOFF; /* экранные — для заливок */
/* РАННИЙ ВЫХОД по окну fore-клипа. Весь узор лежит в прямоугольнике
* x [xh*8, xh*8+32), y [dmy-59, dby]: сверху — первая заливка, снизу —
* нижний декаль, чей низ ровно на dby.
*
* Без этой проверки тайл стены под ногами Кида стоил ~60 000 тактов НА
* НОЛЬ ПИКСЕЛЕЙ: предфильтр fore_tile грубый (габарит 40x78), wpp_fill
* честно режет по окну, но платит ~3 100 тактов за арифметику клипа, а
* pop_wall_b об окне не знает вовсе и идёт в atlas_image + gfx_w0_map,
* чтобы там обнаружить, что рисовать нечего. Два таких тайла (ряд 2 под
* Кидом попадает в fore-окно всегда) = 139 863 такта за кадр, 32 %
* растрового кадра. См. docs/perf_backlog.md, пункт 0. */
if (pop_t_fclip_on) {
int x0 = xh * 8;
if (x0 >= pop_t_fclip_x1 || x0 + 32 <= pop_t_fclip_x0 ||
dmy - 59 >= pop_t_fclip_y1 || dby + 1 <= pop_t_fclip_y0)
return;
}
/* Цвета лежат по рядам 0..2; полоса у потолка (row −1) — это ряд 2
* комнаты сверху, оригинал и сид для неё берёт по ряду 2. */
const uint8_t *cl = pop_pal_wall_col + 44 * (row < 0 ? 2 : row) + col;
@@ -329,13 +378,16 @@ static void wall_pattern_palace(int row, int col, int which_part)
wpp_fill((xh + 2) * 8, dmy - 39, 16, 21, cl[12]);
wpp_fill(xh * 8, dmy - 18, 8, 19, cl[22]);
wpp_fill((xh + 1) * 8, dmy - 18, 24, 19, cl[23]);
pop_wall_b((uint8_t)(wpp_d[0] + 3), (xh + 3) * 8, dmys - 53);
pop_wall_b((uint8_t)(wpp_d[1] + 6), xh * 8, dmys - 34);
pop_wall_b((uint8_t)(wpp_d[2] + 9), xh * 8, dmys - 13);
pop_wall_b((uint8_t)(wpp_d[3] + 12), xh * 8, dmys);
/* Габариты групп из pal_wall.atl: 3..5 = 8x7, 6..8 = 32x12,
* 9..11 = 32x12 (11-й 28x11), 12..14 = 30x5 (14-й 30x4),
* 15..17 = 32x3. Берём максимум по группе. */
wp_blit((uint8_t)(wpp_d[0] + 3), (xh + 3) * 8, dmys - 53, 8, 7);
wp_blit((uint8_t)(wpp_d[1] + 6), xh * 8, dmys - 34, 32, 12);
wp_blit((uint8_t)(wpp_d[2] + 9), xh * 8, dmys - 13, 32, 12);
wp_blit((uint8_t)(wpp_d[3] + 12), xh * 8, dmys, 30, 5);
}
wpp_fill(xh * 8, dby - 2, 32, 3, cl[33]);
pop_wall_b((uint8_t)((which_part ? wpp_d[4] : wpp_d[0]) + 15), xh * 8, dbys);
wp_blit((uint8_t)((which_part ? wpp_d[4] : wpp_d[0]) + 15), xh * 8, dbys, 32, 3);
}
static void wall_pattern(int row, int col, int which_part)
@@ -344,18 +396,29 @@ static void wall_pattern(int row, int col, int which_part)
int dby = 63 * row + 65;
uint8_t parts;
if (pop_palace) { wall_pattern_palace(row, col, which_part); return; }
/* Ранний выход по окну fore-клипа — то же, что у паласной ветки, но свой
* габарит. Самый верхний кусок здесь — ЛЕВАЯ марка: LPOS[4] = 58 плюс её
* высота 10, то есть dby+POP_YOFF−67; самый нижний — нижний разделитель,
* низ ровно на dby+POP_YOFF. По x правая марка уходит до xh*8+46
* (a = 24 при dv<2, ширина 14), берём 48. */
{
int x0 = xh * 8, dbs = dby + POP_YOFF;
if (!wp_vis(x0, 48, dbs - 67, dbs)) return;
}
parts = WP_PARTS[pop_wall_modifier(row, col)];
wall_rnd(row, col);
if (which_part) {
/* Габариты из pop_wall.atl: 13 = 32x21, 11/12 = 9x21. */
if ((parts & WP_RND) && wr_rndblk)
pop_wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
wp_blit(RES_WALL_RNDBLOCK, xh * 8, dby - 42, 32, 21);
if (parts & WP_MID)
pop_wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div),
(xh + 1) * 8 + wr_mid_off, dby - 21);
wp_blit((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div),
(xh + 1) * 8 + wr_mid_off, dby - 21, 9, 21);
}
if (parts & WP_BOT)
pop_wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
wp_blit((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div),
xh * 8 + wr_bot_off, dby, 9, 21);
if (which_part) {
if ((parts & WP_RIGHT) && wr_right)
draw_right_mark(wr_rightv, wr_mid_off, row, col);