diff --git a/applications/PoP/roomtest/pop_cdraw.h b/applications/PoP/roomtest/pop_cdraw.h index c6ace88..7da2c7d 100644 --- a/applications/PoP/roomtest/pop_cdraw.h +++ b/applications/PoP/roomtest/pop_cdraw.h @@ -137,6 +137,20 @@ extern uint8_t pop_cd_batch; void pop_cd_batch_begin(void); void pop_cd_batch_end(void); +/* ПОДАВИТЬ пометки на время серии блитов — для того, кто пометил область САМ + * и заведомо шире, чем нарисует (падающий кусок: pop_loose_mob_tick помечает + * весь коридор heal'а одним вызовом, а спрайт куска — три блита внутри него). + * Дешевле пакета: pop_cd_touch выходит второй проверкой, не считая ни колонок, + * ни рядов. Замер 2026-08-17: настоящий вызов pop_cd_touch = 4 502 такта, + * 28% всей цены блита. + * + * ОПАСНО применять без разбора: пропущенная пометка = персонаж, которого + * стёрли, но не попросили перерисоваться, то есть исчезнувший со экрана + * персонаж (ровно этот баг ловили 2026-08-13 в комнате 23). Ставить только + * там, где покрытие доказано. */ +void pop_cd_mute(void); +void pop_cd_unmute(void); + /* Кого в этом кадре можно НЕ перерисовывать: бит 0 — Кид, бит 1 — соперник. * Звать ДВАЖДЫ: перед heal (стирать нечего) и перед отрисовкой (тик мог * персонажа всё-таки сдвинуть — тогда бит снимется сам, а pop_char_draw diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 08166ad..dbd0afb 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -255,47 +255,64 @@ static void draw_leveldoor(uint8_t modif, int xh, int dmy) } /* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */ +/* ---- КОНТЕКСТ ТАЙЛА (порт load_curr_and_left_tile, seg008:0339) ------- + * Не локали draw_tile, а file-scope переменные — ровно как curr_tile / + * curr_modifier / draw_xh / draw_main_y / draw_bottom_y у оригинала. + * + * Зачем: в draw_tile 57 вызовов, и КАЖДОЕ значение, живое через вызов, SDCC + * спиливал в стековый кадр — получались 26 байт кадра и 513 обращений + * `-N(ix)` (замер 2026-08-17: 23 700 тактов накладных на вызов при одном + * блите на тайл, docs/perf_green_phase.md §1.6). У file-scope переменных + * абсолютная адресация (`ld a,(nn)` — 13 тактов против 19) и спиливать + * нечего. Функция от этого не становится реентерабельной, но и не была: + * draw_tile ниоткуда себя не зовёт. */ +static int dt_row, dt_col; +static uint8_t dt_code, dt_lcode, dt_lmod; +static int dt_xh, dt_x, dt_dby, dt_dmy; +static const piece *dt_t, *dt_lt; +static uint8_t dt_rbl, dt_rblm, dt_wmod, dt_base_id; +static int dt_base_yb; + static void draw_tile(int row, int col) { - uint8_t code = pop_tile_code(row, col); - uint8_t lcode = pop_tile_code(row, col - 1); - uint8_t lmod = pop_tile_mod(row, col - 1); + dt_row = row; dt_col = col; + dt_code = pop_tile_code(dt_row, dt_col); + dt_lcode = pop_tile_code(dt_row, dt_col - 1); + dt_lmod = pop_tile_mod(dt_row, dt_col - 1); /* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener * (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05). * Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого * соседа (его правая грань). */ - if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; } - else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; } - if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } } - else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; } - int xh = POP_COL_XH[col]; - int x = xh * 8; - int dby = 63 * row + 65; - int dmy = dby - 3; - const piece *t = &pop_tile_table[code]; - const piece *lt = &pop_tile_table[lcode]; - uint8_t rbl_code, rbl_mod; - uint8_t wmod; + if (dt_code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 1; } + else if (dt_code == 0x06) { if ((pop_doorlink2(pop_tile_mod(dt_row, dt_col)) & 0x1F) > 1) dt_code = 5; } + if (dt_lcode == 0x0F) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) { dt_lcode = 1; dt_lmod = 0; } } + else if (dt_lcode == 0x06) { if ((pop_doorlink2(dt_lmod) & 0x1F) > 1) dt_lcode = 5; } + dt_xh = POP_COL_XH[dt_col]; + dt_x = dt_xh * 8; + dt_dby = 63 * dt_row + 65; + dt_dmy = dt_dby - 3; + dt_t = &pop_tile_table[dt_code]; + dt_lt = &pop_tile_table[dt_lcode]; /* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0 - * комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] = - * room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */ - if (row + 1 > 2) { - int lc = col - 1; + * комнаты снизу (load_rowbelow, seg008:368): row_below_left_[dt_col] = + * room_B(0, dt_col-1); dt_col-1<0 → левая комната снизу = стена уровня. */ + if (dt_row + 1 > 2) { + int lc = dt_col - 1; /* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате * (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда; * «стена» там стоит, только если такой комнаты нет — как в * load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */ - if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20; - else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 0; + if (lc < 0) dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20; + else dt_rbl = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 0; /* Модификатора ряда снизу у нас нет (pop_t_below — только fg), а он * нужен паласной панели над воротами. Ставим 0 = «панели нет»: * случай узкий (ворота в комнате СНИЗУ, по диагонали от пустого * тайла нижнего ряда), но расхождение с оригиналом честное. */ - rbl_mod = 0; + dt_rblm = 0; } else { - rbl_code = pop_tile_code(row + 1, col - 1); - rbl_mod = pop_tile_mod(row + 1, col - 1); + dt_rbl = pop_tile_code(dt_row + 1, dt_col - 1); + dt_rblm = pop_tile_mod(dt_row + 1, dt_col - 1); } /* Пометка «фон трогали» — ОДНА на весь тайл, а не на каждый его кусок @@ -306,72 +323,72 @@ static void draw_tile(int row, int col) /* --- draw_tile_anim_topright (seg008:0568): верх ВОРОТ снизу-слева. * Условие дословно: свой тайл пуст / верх большой колонны / верх двери, * а снизу-слева — ворота. --- */ - if ((code == 0 || code == 9 || code == 12) && rbl_code == 4) { - uint8_t om = (rbl_mod > 188) ? 188 : rbl_mod; - pop_env_b(GATE_TOP_MASK, x, dby); /* маска (mono, чёрным) */ - pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], x, dby); + if ((dt_code == 0 || dt_code == 9 || dt_code == 12) && dt_rbl == 4) { + uint8_t om = (dt_rblm > 188) ? 188 : dt_rblm; + pop_env_b(GATE_TOP_MASK, dt_x, dt_dby); /* маска (mono, чёрным) */ + pop_env_b(DOOR_FRAM_TOP[(om >> 2) & 7], dt_x, dt_dby); } /* --- floorright (seg008.c:392) — только can_see_bottomleft --- */ - if (code == 0 || code == 9 || code == 12 || code == 26) { - draw_topright(rbl_code, rbl_mod, x, dby); + if (dt_code == 0 || dt_code == 9 || dt_code == 12 || dt_code == 26) { + draw_topright(dt_rbl, dt_rblm, dt_x, dt_dby); /* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op * (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет - * floor_right: lt->floor_right. */ + * floor_right: dt_lt->floor_right. */ } /* --- draw_tile_right (seg008.c:456) --- */ - if (code != 20) { - if (lcode == 20) { + if (dt_code != 20) { + if (dt_lcode == 20) { /* Полоска на стене (seg008:510) — только палас и только если у * стены не взведён старший бит модификатора. */ - if (PALACE && (lmod & 0x80) == 0) - pop_env_b(84, x + 3 * 8, dmy - 27); - pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */ - } else if (lcode == 0) { - if (lmod <= 3 && BLUELINE_FRAM1[lmod]) - pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy); - } else if (lcode == 1) { + if (PALACE && (dt_lmod & 0x80) == 0) + pop_env_b(84, dt_x + 3 * 8, dt_dmy - 27); + pop_wall_b(1, dt_x, pop_tile_table[20].right_y + dt_dmy); /* грань стены */ + } else if (dt_lcode == 0) { + if (dt_lmod <= 3 && BLUELINE_FRAM1[dt_lmod]) + pop_env_b(BLUELINE_FRAM1[dt_lmod], dt_x, BLUELINE_FRAM_Y[dt_lmod] + dt_dmy); + } else if (dt_lcode == 1) { uint8_t num; - pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */ - num = (lmod <= 3) ? lmod : 0; + pop_env_b(42, dt_x, pop_tile_table[1].right_y + dt_dmy); /* правый треуг. пола */ + num = (dt_lmod <= 3) ? dt_lmod : 0; /* seg008:501 — `if (num == !!level_type) return;`: в подземелье * пропускается num==0, в паласе num==1. */ if (num != (PALACE ? 1 : 0)) - pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */ - } else if (lcode == 7 || lcode == 12) { + pop_env_b(BLUELINE_FRAM3[num], dt_x, dt_dmy - 20); /* силуэт кладки */ + } else if (dt_lcode == 7 || dt_lcode == 12) { /* Низ декоративной панели над воротами (seg008:506) — только * палас; в подземелье у doortop right_id нулевой и ветка пустая. */ - if (PALACE && DOORTOP_FRAM_BOT[lmod & 3]) - pop_env_b(DOORTOP_FRAM_BOT[lmod & 3], x, lt->right_y + dmy); + if (PALACE && DOORTOP_FRAM_BOT[dt_lmod & 3]) + pop_env_b(DOORTOP_FRAM_BOT[dt_lmod & 3], dt_x, dt_lt->right_y + dt_dmy); } else { - if (lt->right_id) - pop_env_b(lt->right_id, x, lt->right_y + dmy); + if (dt_lt->right_id) + pop_env_b(dt_lt->right_id, dt_x, dt_lt->right_y + dt_dmy); /* Орнаментная лента соседа (seg008:486) — только палас. Она и * даёт непрерывную полосу под окнами: stripe_id = 145 у пола, * кнопок, зелья, loose, чомпера и меча. */ - if (PALACE && lt->stripe_id) - pop_env_b(lt->stripe_id, x, dmy - 27); + if (PALACE && dt_lt->stripe_id) + pop_env_b(dt_lt->stripe_id, dt_x, dt_dmy - 27); /* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */ - if (lcode == 19 || lcode == 30) - pop_env_b(146, x, dby - 28); + if (dt_lcode == 19 || dt_lcode == 30) + pop_env_b(146, dt_x, dt_dby - 28); } } /* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для - * верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от + * верхней полосы (dt_row<0, draw_tile_aboveroom) НЕ рисуется + защита от * отрицательного индекса в pop_loose_modif. --- */ - if (row >= 0) { - if (lcode == 4) { /* gate: решётка */ - pop_gate_back_b(lmod, xh, dby, dmy); - } else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */ - pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1); - } else if (lcode == 2) { /* spike: статич. кадр */ - uint8_t sf = (lmod & 0x80) ? 5 : lmod; + if (dt_row >= 0) { + if (dt_lcode == 4) { /* gate: решётка */ + pop_gate_back_b(dt_lmod, dt_xh, dt_dby, dt_dmy); + } else if (dt_lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */ + pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[dt_row * 10 + (dt_col - 1)])], dt_x, dt_dby - 1); + } else if (dt_lcode == 2) { /* spike: статич. кадр */ + uint8_t sf = (dt_lmod & 0x80) ? 5 : dt_lmod; if (sf < 10 && SPIKES_FRAM_RIGHT[sf]) - pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7); - } else if (lcode == 16) { /* дверь уровня */ - draw_leveldoor(lmod, xh, dmy); + pop_env_b(SPIKES_FRAM_RIGHT[sf], dt_x, dt_dmy - 7); + } else if (dt_lcode == 16) { /* дверь уровня */ + draw_leveldoor(dt_lmod, dt_xh, dt_dmy); } /* Пламя факела (seg008:560, chtab_1 id modifier+1) здесь, в статичной * отрисовке тайла, не рисуем — его ведёт pop_torch_draw из @@ -381,101 +398,101 @@ static void draw_tile(int row, int col) } /* --- draw_tile_bottom (seg008.c:570) --- */ - if (code == 20) { - wmod = pop_wall_modifier(row, col); + if (dt_code == 20) { + dt_wmod = pop_wall_modifier(dt_row, dt_col); if (!PALACE) /* во дворце тело стены даёт */ - pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* wall_pattern */ + pop_wall_b(POP_WALL_FRAM_BOTTOM[dt_wmod], dt_x, dt_dby); /* wall_pattern */ /* заливками (seg008:576) */ - pop_wall_pattern_b(row, col, 0); - } else if (t->bottom_id) { - pop_env_b(t->bottom_id, x, dby); + pop_wall_pattern_b(dt_row, dt_col, 0); + } else if (dt_t->bottom_id) { + pop_env_b(dt_t->bottom_id, dt_x, dt_dby); } /* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В - * верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху): - * её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */ - if (code == 11) - pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col] - : pop_ceil_modif[col])], x, dby); + * верхней полосе (dt_row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху): + * её состояние живёт отдельно, в pop_ceil_modif[dt_col] (pop_map). --- */ + if (dt_code == 11) + pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(dt_row >= 0 ? pop_loose_modif[dt_row * 10 + dt_col] + : pop_ceil_modif[dt_col])], dt_x, dt_dby); - /* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ + /* --- draw_tile_base (seg008.c:611). Верхняя полоса (dt_row<0) базу НЕ * рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */ - if (row >= 0) { - uint8_t base_id = t->base_id; - int base_yb = dmy; - if (lcode == 26 && code == 12) { + if (dt_row >= 0) { + dt_base_id = dt_t->base_id; + dt_base_yb = dt_dmy; + if (dt_lcode == 26 && dt_code == 12) { /* «Lattice + door A» (seg008:622): у doortop своей базы нет - * (base_id 0), и когда слева от него стоит арка (lattice_down), + * (dt_base_id 0), и когда слева от него стоит арка (lattice_down), * пара рисуется ОДНИМ спрайтом 6, опущенным на 3 px. Без этой * ветки на месте тайла оставалась чёрная дыра — уровень 5, * комната 12, тайл (0,9) (BUG-LATTICE-DOORTOP). */ - base_id = 6; - base_yb += 3; + dt_base_id = 6; + dt_base_yb += 3; } - else if (code == 11) /* loose: левая грань (анимир.) */ - base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])]; - else if (code == 0x0F && lcode == 0 && !PALACE) /* opener, слева пусто */ - base_id = 148; /* левая половина без пола — только */ + else if (dt_code == 11) /* loose: левая грань (анимир.) */ + dt_base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[dt_row * 10 + dt_col])]; + else if (dt_code == 0x0F && dt_lcode == 0 && !PALACE) /* opener, слева пусто */ + dt_base_id = 148; /* левая половина без пола — только */ /* подземелье (seg008:628) */ - if (base_id) - pop_env_b(base_id, x, t->base_y + base_yb); + if (dt_base_id) + pop_env_b(dt_base_id, dt_x, dt_t->base_y + dt_base_yb); } /* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в * СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики * рисуется только правым соседом со сдвигом → шахта над самой пикой * пустая. */ - if (row >= 0 && code == 2) { - uint8_t sm = pop_tile_mod(row, col); + if (dt_row >= 0 && dt_code == 2) { + uint8_t sm = pop_tile_mod(dt_row, dt_col); uint8_t sf = (sm & 0x80) ? 5 : sm; if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf]) - pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2); + pop_env_b(POP_SPIKES_FRAM_LEFT[sf], dt_x, dt_dmy - 2); } /* Чомпер (тот же draw_tile_anim, case tiles_18_chomper): нижняя челюсть * на draw_main_y, поверх неё кровь (если кого-то перемололо), затем * верхняя челюсть со своим подъёмом. Всё это BACKtable — то есть ПОД * персонажем; передний слой рисует fore-проход. */ - if (row >= 0 && code == 0x12) { - uint8_t cm = pop_tile_mod(row, col); + if (dt_row >= 0 && dt_code == 0x12) { + uint8_t cm = pop_tile_mod(dt_row, dt_col); uint8_t pose = pop_chomp_pose(cm); - pop_env_b(CHOMP_FRAM_BOT[pose], x, dmy); + pop_env_b(CHOMP_FRAM_BOT[pose], dt_x, dt_dmy); if (cm & 0x80) - pop_env_b((uint8_t)(pose + 114), x + 8, dmy - 6); + pop_env_b((uint8_t)(pose + 114), dt_x + 8, dt_dmy - 6); if (CHOMP_FRAM_TOP[pose]) - pop_env_b(CHOMP_FRAM_TOP[pose], x, dmy - CHOMP_FRAM_Y[pose]); + pop_env_b(CHOMP_FRAM_TOP[pose], dt_x, dt_dmy - CHOMP_FRAM_Y[pose]); } /* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword): * chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3. * В оригинале это midtable (рисуется вместе с объектами) — у нас статикой * в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */ - if (row >= 0 && code == 22) - pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3); + if (dt_row >= 0 && dt_code == 22) + pop_pot_b((uint8_t)((pop_tile_mod(dt_row, dt_col) == 1) + 10), dt_x, dt_dmy - 3); /* --- draw_tile_fore (seg008.c:690) --- */ - if (code == 20) { - wmod = pop_wall_modifier(row, col); + if (dt_code == 20) { + dt_wmod = pop_wall_modifier(dt_row, dt_col); if (!PALACE) /* seg008:711 */ - pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */ - pop_wall_pattern_b(row, col, 1); + pop_wall_b(POP_WALL_FRAM_MAIN[dt_wmod], dt_x, dt_dmy); /* основная грань */ + pop_wall_pattern_b(dt_row, dt_col, 1); } - if (code == 0x12) { /* чомпер: передние зубья + кровь. + if (dt_code == 0x12) { /* чомпер: передние зубья + кровь. * Отдельной веткой, как в оригинале: * у записи 0x12 fore_id == 0, и через * таблицу этот слой не идёт. */ - uint8_t cm = pop_tile_mod(row, col); + uint8_t cm = pop_tile_mod(dt_row, dt_col); uint8_t pose = pop_chomp_pose(cm); /* Передние куски чомпера и его кровь лежат в pop_fore.atl * (FORE_ENV_IDS в pop_pack_bg.py), а НЕ в env-страницах: блит * обязан быть pop_fore_b, иначе id уходит в чужую страницу. */ - pop_fore_b(POP_CHOMP_FRAM_FOR[pose], x, dmy); + pop_fore_b(POP_CHOMP_FRAM_FOR[pose], dt_x, dt_dmy); if (cm & 0x80) - pop_fore_b((uint8_t)(pose + 119), x + 8, dmy - 6); + pop_fore_b((uint8_t)(pose + 119), dt_x + 8, dt_dmy - 6); } - if (code == 10) { /* зелье: chtab_1, не env fore_id. + if (dt_code == 10) { /* зелье: chtab_1, не env fore_id. * Пузырёк — динамика (pop_potion_draw). */ - pop_potion_flask(row, col, pop_tile_mod(row, col)); - } else if (t->fore_id) { - pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy); + pop_potion_flask(dt_row, dt_col, pop_tile_mod(dt_row, dt_col)); + } else if (dt_t->fore_id) { + pop_fore_b(dt_t->fore_id, dt_t->fore_x * 8 + dt_x, dt_t->fore_y + dt_dmy); } pop_cd_batch_end(); /* одна пометка на весь тайл */ } @@ -595,9 +612,15 @@ void pop_ceil_shake_draw(int col) __banked pop_dbg_m13(); /* ЗАМЕР: вход pop_ceil_shake_draw */ pop_heal_off(x, 0, 64, CEIL_BAND_H); pop_dbg_m14(); /* ЗАМЕР: heal сделан */ + /* Пометка «фон трогали» — ОДНА на восстановленный прямоугольник: пока + * стоит окно (ниже), pop_blit_b пометок не ставит. Область та же, что у + * heal'а, — он её и стирает. */ + pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H); gfx_set_bank(GFX_BANK_SPRITE); pop_t_clip_top = POP_YOFF; + pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H); /* восстанавливаем ровно полосу */ draw_tile(-1, col); + pop_t_win_clear(); pop_t_clip_top = 0; gfx_set_bank(GFX_BANK_NORMAL); pop_dbg_m15(); /* ЗАМЕР: draw_tile сделан */ @@ -606,11 +629,17 @@ void pop_ceil_shake_draw(int col) __banked void pop_ceil_bake_empty(int col) __banked { int x = POP_COL_XH[col] * 8; - pop_cd_touch(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* bar мимо pop_blit_b */ gfx_set_bank(GFX_BANK_NORMAL); + /* Отдельного pop_cd_touch тут не надо: pop_bar_black метит сам, и ровно + * этот же прямоугольник (было два вызова на одну область). */ pop_bar_black(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* стереть плиту в ОЗУ-копии */ gfx_set_bank(GFX_BANK_TRANSPARENT); pop_t_clip_top = POP_YOFF; + /* Окно = ровно стёртая полоса. Главный выигрыш здесь не в отсеве, а в + * КЛИПЕ: куски ряда 0 высотой 63 px рисовались целиком (клип был только + * сверху, по pop_t_clip_top), хотя восстановить надо девять строк — + * отсюда 21 447 такта на блит против 14 384 в pop_floor_bake. */ + pop_t_win_set(x, POP_YOFF, 64, CEIL_BAND_H + 1); pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */ /* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только * два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей @@ -629,6 +658,7 @@ void pop_ceil_bake_empty(int col) __banked for (c = c0; c <= c1; c++) draw_tile(0, c); } pop_t_bake_rest = 0; + pop_t_win_clear(); pop_t_clip_top = 0; gfx_set_bank(GFX_BANK_NORMAL); } @@ -734,14 +764,22 @@ void pop_torch_wipe(int row, int col) __banked void pop_floor_bake(int row, int col) __banked { int x = POP_COL_XH[col] * 8; - pop_cd_touch(x, POP_YOFF + 63 * row + 26, 60, 39); /* bar мимо pop_blit_b */ int yb = POP_YOFF + 63 * row; + /* Пометку ставит сам pop_bar_black — по тому же прямоугольнику; отдельный + * pop_cd_touch тут был вторым вызовом на одну область (4 502 такта). */ /* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола. * НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже * (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */ gfx_set_bank(GFX_BANK_NORMAL); pop_bar_black(x, yb + 26, 60, 39); gfx_set_bank(GFX_BANK_TRANSPARENT); + /* ОКНА КЛИПА ЗДЕСЬ НЕТ ОСОЗНАННО (замер 2026-08-17). Оно попробовано и + * откачено: у этого тайла клипа нет вовсе, то есть блиты идут БЫСТРЫМ + * путём (gfx_blit_noclip, 10 422), а окно уводит их в blit_b_clip + * (14 088) — и не отсеивает ни одного куска (7,6 блита до и после). + * Итог был 187 266 -> 198 279, то есть хуже. Вернуться к этой идее + * после того, как blit_b_clip перестанет платить за 22-байтовый + * стековый кадр (позиция G3 в docs/perf_green_phase.md). */ pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */ draw_tile(row, col); if (col + 1 < 10) draw_tile(row, col + 1); @@ -886,6 +924,29 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254}; static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25}; #define MOB_Y_NONE 0x7FFF +/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26) + * с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46: + * ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину + * [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной + * странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */ +#define MOB_X0(mx) ((mx) - 4) +/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом». + * След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение + * трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y). + * + * Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля. + * А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой + * операции кадра, помноженной на число падающих плит; сужена до 24. + * + * ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не + * подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в + * memory про акселератор таких требований нет. Единственный источник — + * наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить + * ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */ +#define MOB_W 64 /* mob_x-4 .. mob_x+60 */ +#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */ +#define MOB_HEAL_H 24 /* .. mob_y + 4 */ + /* Свободный слот = не летит И дочистка завершена (иначе затрём хвост, * который ещё не стёрт со второй страницы). Общий для обычного отрыва и * для копии из loose_fall — раньше цикл был написан дважды. */ @@ -949,6 +1010,13 @@ static void mob_spawn_copy(const mob_t *src) m->clean = 0; m->defer = 0; m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE; + /* Кусок рождается ВНУТРИ mob_tick_one (loose_fall сбил плиту), и слот ему + * mob_alloc выдаёт с начала таблицы — то есть, скорее всего, УЖЕ + * пройденный циклом pop_loose_mob_tick. Своей пометки «фон трогали» этот + * кусок в текущем кадре не получит, а нарисован будет; помечаем коридор + * здесь — иначе персонаж под ним стирается и не просит перерисовки + * (см. pop_cd_mute в mob_render). */ + pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64); } /* Отрыв куска в ЧУЖОЙ комнате: плита, которую Кид расшатал и убежал, всё @@ -960,28 +1028,6 @@ void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked mob_spawn(room, row, col); } -/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26) - * с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46: - * ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину - * [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной - * странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */ -#define MOB_X0(mx) ((mx) - 4) -/* Коридор heal — по РЕАЛЬНОМУ следу спрайта, а не «с запасом». - * След: по x mob_x .. mob_x+58, по y mob_y-16 .. mob_y — это объединение - * трёх частей (41 = 32x13 с y-3, 42 = 26x15 с x+32,y-1, 43 = 32x3 с y). - * - * Ширина оставлена прежней (64 = mob_x-4 .. mob_x+60): нужных 62 плюс поля. - * А вот высота была 32 при нужных 16 — вдвое лишнего на самой дорогой - * операции кадра, помноженной на число падающих плит; сужена до 24. - * - * ПРО ЗАПАС ПО ШИРИНЕ: обоснования «выравнивание блока акселератора» я не - * подтвердил — ни pop_heal_fast, ни gfx_heal координаты не округляют, а в - * memory про акселератор таких требований нет. Единственный источник — - * наблюдение в pop_clip_sprite про 1-2 px слайверы правой грани. Сузить - * ширину можно, но ТОЛЬКО подтвердив прогоном, что слайверы не появятся. */ -#define MOB_W 64 /* mob_x-4 .. mob_x+60 */ -#define MOB_HEAL_UP 20 /* верх коридора = mob_y − 20 */ -#define MOB_HEAL_H 24 /* .. mob_y + 4 */ /* Остановить падающий кусок (при смене комнаты — состояние per-tile не * должно утечь в новую комнату). */ @@ -1306,13 +1352,38 @@ static void mob_render(mob_t *m, uint8_t pg) * Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и * «цельная ровная плита» вместо двух частей со сдвигом правой половины * на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). */ - pop_env_b(74, m->x, m->y); - pop_env_b(70, m->x, m->y - 3); - pop_env_b(72, m->x + 32, m->y - 1); + /* Пометки «фон трогали» на время трёх блитов ПОДАВЛЕНЫ: коридор куска + * (MOB_X0 .. +MOB_W, y−27 .. +37) уже помечен ОДНИМ вызовом — из + * pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска, + * рождённого внутри тика. Спрайт целиком внутри коридора: он занимает + * x m->x .. m->x+57 (правая часть 72 идёт с x+32, ширина 26) при + * коридоре m->x−4 .. m->x+59, и y m->y−15 .. m->y при коридоре + * m->y−27 .. m->y+37 с запасом на ускорение (speed ≤ 31 за кадр). + * Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на + * кусок, то есть 81 000 в кадре при шести кусках. */ + pop_cd_mute(); + /* ВЫХОД ЗА ПОЛЕ РЕЖЕМ САМИ, а не чистим борта после. Раньше здесь стоял + * pop_clip_sprite, то есть кусок рисовался в борт целиком, взводил + * border_dirty, и pop_room_clip_borders потом стирал ДВЕ полосы во всю + * ширину экрана — 320x28 и 320x28. Замер 2026-08-17: 150 978 тактов в + * КАЖДОМ кадре, пока хоть один кусок торчит выше поля, то есть почти весь + * каскад (гряда на 13-м рождается ровно у потолка, y = 2). Это была + * четверть цианового блока. + * + * Окно ставим только когда кусок реально задевает борт: внутри поля блиты + * идут быстрым путём (10 422 против 14 088). */ + { + int ytop = m->y - 16 + POP_YOFF, ybot = m->y + POP_YOFF; + uint8_t clipped = (uint8_t)(ytop < POP_YOFF || ybot >= POP_CLIP_BOTTOM); + if (clipped) pop_t_win_set(0, POP_YOFF, 320, POP_PLAYFIELD_H); + pop_env_b(74, m->x, m->y); + pop_env_b(70, m->x, m->y - 3); + pop_env_b(72, m->x + 32, m->y - 1); + if (clipped) pop_t_win_clear(); + } + pop_cd_unmute(); m->prev_y[pg] = m->y; gfx_set_bank(GFX_BANK_NORMAL); - /* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */ - pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30); } /* Отрисовка кусков, идущих ПОД Kid. Звать ПОСЛЕ pop_redraw_needed(): фон diff --git a/applications/PoP/roomtest/pop_tile.c b/applications/PoP/roomtest/pop_tile.c index 73b9e0d..16d4b7c 100644 --- a/applications/PoP/roomtest/pop_tile.c +++ b/applications/PoP/roomtest/pop_tile.c @@ -246,11 +246,20 @@ void pop_cd_batch_end(void) pop_cd_touch(cdb_x0, cdb_y0, cdb_x1 - cdb_x0 + 1, cdb_y1 - cdb_y0 + 1); } +/* Подавление на серию блитов — см. шапку в pop_cdraw.h. Отдельным значением + * того же флага, а не вторым флагом: у pop_cd_touch тогда остаётся ОДНА + * проверка на общем пути. */ +#define CD_BATCH_MUTE 2 + +void pop_cd_mute(void) { pop_cd_batch = CD_BATCH_MUTE; } +void pop_cd_unmute(void) { pop_cd_batch = 0; } + void pop_cd_touch(int x, int y, int w, int h) { uint16_t cols; uint8_t r0, r1; if (pop_cd_batch) { /* копим, не разбирая на колонки/ряды */ + if (pop_cd_batch == CD_BATCH_MUTE) return; /* область помечена вызывающим */ int x1 = x + w - 1, y1 = y + h - 1; if (x < cdb_x0) cdb_x0 = x; if (y < cdb_y0) cdb_y0 = y; @@ -310,12 +319,65 @@ void pop_cd_clear(uint8_t p) * перерисовка тайлов — идёт с pop_t_fclip_on == 0. */ #define FLIP_TOP(top, h) POP_FLIP_TOP(top, h) /* формула — в pop_bg.h */ -static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h) +/* Окно под точечную перерисовку/запечку — контракт в шапке pop_tile.h. + * Логическая пара нужна для отсева тайлов (он считается из ряда комнаты), + * экранная — для клипа самого блита; при перевороте они разные. */ +void pop_t_win_set(int x, int ytop, int w, int h) { - int dx = x, dy = top, dw = w, dh = h, sx = 0, sy = 0; + pop_t_fclip_x0 = x; + pop_t_fclip_x1 = x + w; + pop_t_fclip_ly0 = ytop; + pop_t_fclip_ly1 = ytop + h; + if (pop_upside) { + int t = FLIP_TOP(ytop, h); + pop_t_fclip_y0 = t; + pop_t_fclip_y1 = t + h; + } else { + pop_t_fclip_y0 = ytop; + pop_t_fclip_y1 = ytop + h; + } + pop_t_fclip_on = 1; +} + +void pop_t_win_clear(void) { pop_t_fclip_on = 0; } + +/* Клипованный путь блита. Габарит и смещения внутри куска — БАЙТЫ, а вся + * рабочая переменная — file-scope, НЕ локали. + * + * Корректность байтового габарита по построению: кадры наших атласов не + * крупнее 56x63 (просканированы все 109 .atl, docs/perf_l13_room23.md §5), а + * кадр больше 255 в pop_blit_b уходит в blit_b_oversize и сюда не попадает. + * + * Зачем file-scope: с локалями SDCC заводил 22-байтовый стековый кадр и 211 + * обращений `-N(ix)` — при ~46 замеренных тактах на обращение это ~9 700 + * такта из 14 088 цены клипованного блита (замер 2026-08-17, + * docs/perf_green_phase.md §1.6). Одного байтового габарита НЕ ХВАТИЛО: + * кадр остался, обращений стало 173 — значений, живых через шесть вызовов + * ядер libbgi, всё равно больше, чем регистров у Z80. С file-scope + * спиливать нечего, а адресация абсолютная (13 тактов против 19). + * Тот же приём и по той же причине применён к draw_tile (pop_room.c). + * + * Реентерабельности не было и не требуется: единственный вызывающий — + * pop_blit_b, и он себя не зовёт. + * + * Арифметика клипа считается в int16 (bc_d), в байт уходит только результат — + * иначе вычитание ушло бы в перенос по модулю 256. */ +static const uint8_t *bc_img; +static int bc_dx, bc_dy; +static int16_t bc_d; +static uint8_t bc_w, bc_h, bc_dw, bc_dh, bc_sx, bc_sy; + +static void blit_b_clip(const uint8_t *img, int x, int top, uint8_t w, uint8_t h) +{ + bc_img = img; bc_w = w; bc_h = h; + bc_dx = x; bc_dy = top; + bc_dw = w; bc_dh = h; bc_sx = 0; bc_sy = 0; if (!pop_upside) { - if (pop_t_clip_top && dy < pop_t_clip_top) { /* полоса у потолка */ - sy = pop_t_clip_top - dy; dy = pop_t_clip_top; dh -= sy; + if (pop_t_clip_top && bc_dy < pop_t_clip_top) { /* полоса у потолка */ + bc_d = (int16_t)(pop_t_clip_top - bc_dy); + if (bc_d >= (int16_t)bc_dh) return; + bc_sy = (uint8_t)bc_d; bc_dy = pop_t_clip_top; + bc_dh = (uint8_t)(bc_dh - bc_sy); } } else { /* ПЕРЕВОРОТ: то, что в обычном виде торчало ВЫШЕ поля (полоса кладки @@ -323,56 +385,95 @@ static void blit_b_clip(const uint8_t *img, int x, int top, int w, int h) * где живёт полоса HP (баг «мусор ниже 192», найден пользователем). * Режем по границам поля ВСЕГДА, а не по pop_t_clip_top: зеркальный * спрайт может вылезти и там, где в обычном виде клип не ставили. */ - if (dy < POP_YOFF) { sy = POP_YOFF - dy; dy = POP_YOFF; dh -= sy; } - if (dy + dh > POP_CLIP_BOTTOM) dh = POP_CLIP_BOTTOM - dy; + if (bc_dy < POP_YOFF) { + bc_d = (int16_t)(POP_YOFF - bc_dy); + if (bc_d >= (int16_t)bc_dh) return; + bc_sy = (uint8_t)bc_d; bc_dy = POP_YOFF; + bc_dh = (uint8_t)(bc_dh - bc_sy); + } + if (bc_dy + bc_dh > POP_CLIP_BOTTOM) { + bc_d = (int16_t)(POP_CLIP_BOTTOM - bc_dy); + if (bc_d <= 0) return; + bc_dh = (uint8_t)bc_d; + } } - if (pop_t_fclip_on) { /* окно fore-слоя */ - if (dx < pop_t_fclip_x0) { int d = pop_t_fclip_x0 - dx; sx += d; dx += d; dw -= d; } - if (dy < pop_t_fclip_y0) { int d = pop_t_fclip_y0 - dy; sy += d; dy += d; dh -= d; } - if (dx + dw > pop_t_fclip_x1) dw = pop_t_fclip_x1 - dx; - if (dy + dh > pop_t_fclip_y1) dh = pop_t_fclip_y1 - dy; + if (pop_t_fclip_on) { /* окно fore-слоя / перерисовки */ + if (bc_dx < pop_t_fclip_x0) { + bc_d = (int16_t)(pop_t_fclip_x0 - bc_dx); + if (bc_d >= (int16_t)bc_dw) return; + bc_sx = (uint8_t)(bc_sx + (uint8_t)bc_d); + bc_dx += bc_d; bc_dw = (uint8_t)(bc_dw - (uint8_t)bc_d); + } + if (bc_dy < pop_t_fclip_y0) { + bc_d = (int16_t)(pop_t_fclip_y0 - bc_dy); + if (bc_d >= (int16_t)bc_dh) return; + bc_sy = (uint8_t)(bc_sy + (uint8_t)bc_d); + bc_dy += bc_d; bc_dh = (uint8_t)(bc_dh - (uint8_t)bc_d); + } + if (bc_dx + bc_dw > pop_t_fclip_x1) { + bc_d = (int16_t)(pop_t_fclip_x1 - bc_dx); + if (bc_d <= 0) return; + bc_dw = (uint8_t)bc_d; + } + if (bc_dy + bc_dh > pop_t_fclip_y1) { + bc_d = (int16_t)(pop_t_fclip_y1 - bc_dy); + if (bc_d <= 0) return; + bc_dh = (uint8_t)bc_d; + } } - if (dw <= 0 || dh <= 0) return; + if (!bc_dw || !bc_dh) return; /* ПЕРЕВОРОТ: экранная строка (dy+i) берёт источник (h-1-i), поэтому * обрезка СВЕРХУ экрана съедает НИЖНИЕ строки источника. В терминах * куска источника это sy' = h - sy - dh (при обрезке только сверху даёт * 0, при обрезке только снизу — верхний край). */ + /* Проверки `dw < 256 && dh < 256 && w < 256` убраны как тождественные: + * это уже байты. */ 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); + bc_d = (int16_t)bc_h - (int16_t)bc_sy - (int16_t)bc_dh; + if (bc_d < 0) bc_d = 0; + if (bc_dx >= 0 && bc_dy >= 0 && + bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256) + gfx_blit_part_noclip_vflip(bc_dx, bc_dy, bc_img, bc_sx, + (uint8_t)bc_d, bc_dw, bc_dh); else - gfx_blit_part_vflip(dx, dy, img, sx, fsy, dw, dh); + gfx_blit_part_vflip(bc_dx, bc_dy, bc_img, bc_sx, + (uint8_t)bc_d, bc_dw, bc_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) - gfx_blit_noclip(dx, dy, img); + if (bc_dx >= 0 && bc_dy >= 0 && + bc_dx + bc_dw <= 320 && bc_dy + bc_dh <= 256) { + if (bc_sx == 0 && bc_sy == 0 && bc_dw == bc_w && bc_dh == bc_h) + gfx_blit_noclip(bc_dx, bc_dy, bc_img); else - gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy, - (uint8_t)dw, (uint8_t)dh); + gfx_blit_part_noclip(bc_dx, bc_dy, bc_img, bc_sx, bc_sy, + bc_dw, bc_dh); } else { - gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана */ + gfx_blit_part(bc_dx, bc_dy, bc_img, bc_sx, bc_sy, bc_dw, bc_dh); } /* Кусок ФОНА лёг туда, где может лежать уже нарисованный персонаж — * пометить область (DRAW-COST, pop_cdraw.h). Признак «это не fore-проход * поверх персонажа» — окно клипа: его ставит только он. */ - if (!pop_t_fclip_on) pop_cd_touch(dx, dy, dw, dh); + if (!pop_t_fclip_on) pop_cd_touch(bc_dx, bc_dy, bc_dw, bc_dh); } /* Кадр шире или выше 255 — общий (медленный) путь. Вынесен отдельно, чтобы - * 16-битная арифметика габарита не жила в горячем pop_blit_b: у нас таких - * кадров нет вовсе (максимум 32x63), но контракт формата .atl их допускает. */ + * 16-битная арифметика габарита не жила ни в горячем pop_blit_b, ни в + * blit_b_clip (у того габарит теперь БАЙТОВЫЙ, см. его шапку). У нас таких + * кадров в игровом кадре нет вовсе (максимум 56x63), но контракт формата + * .atl их допускает, а интро/финал такие подложки принесут (320x200) — + * см. docs/perf_l13_room23.md §5. + * + * Клипа полосы у потолка и окна перерисовки здесь нет намеренно: такой кадр + * — полноэкранная подложка, ей ни то, ни другое не адресовано; за клип по + * экрану отвечает само ядро gfx_blit_part. */ static void blit_b_oversize(const uint8_t *img, int x, int ybottom) { uint16_t w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); uint16_t h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); int top = ybottom - (int)h + 1 + POP_YOFF; if (pop_upside) top = FLIP_TOP(top, h); - if (w && h) blit_b_clip(img, x, top, (int)w, (int)h); + if (!w || !h) return; + gfx_blit_part(x, top, img, 0, 0, (int)w, (int)h); + if (!pop_t_fclip_on) pop_cd_touch(x, top, (int)w, (int)h); } void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) @@ -429,7 +530,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) /* В перевёрнутом виде быстрый путь годится, только если спрайт * целиком внутри поля: иначе он залезет в борт (см. blit_b_clip). */ if (pop_upside && (top < POP_YOFF || top + (int)h > POP_CLIP_BOTTOM)) { - blit_b_clip(img, x, top, (int)w, (int)h); + blit_b_clip(img, x, top, w, h); gfx_w0_unmap(); return; } @@ -447,7 +548,7 @@ void pop_blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) pop_cd_touch(x, top, (int)w, (int)h); pop_dbg_b4(); /* ВРЕМЕННО */ } else { - blit_b_clip(img, x, top, (int)w, (int)h); + blit_b_clip(img, x, top, w, h); pop_dbg_b3(); pop_dbg_b4(); /* ВРЕМЕННО: медленный путь целиком в b3 */ } } diff --git a/applications/PoP/roomtest/pop_tile.h b/applications/PoP/roomtest/pop_tile.h index bb0450c..de6dd71 100644 --- a/applications/PoP/roomtest/pop_tile.h +++ b/applications/PoP/roomtest/pop_tile.h @@ -77,6 +77,25 @@ extern int pop_t_fclip_x0, pop_t_fclip_y0, pop_t_fclip_x1, pop_t_fclip_y1; extern int pop_t_fclip_ly0, pop_t_fclip_ly1; extern uint8_t pop_t_fclip_on; +/* Поставить то же окно под ТОЧЕЧНУЮ ПЕРЕРИСОВКУ / ЗАПЕЧКУ. Прямоугольник — + * тот, который восстанавливаем (heal или бар-в-чёрный), в ЛОГИЧЕСКИХ + * экранных координатах (с POP_YOFF, но до переворота); экранную пару + * функция считает сама. + * + * Зачем: точечная перерисовка зовёт draw_tile, а тот честно рисует ВЕСЬ тайл. + * У полосы кладки у потолка это особенно дорого: куски ряда 0 высотой 63 px + * рисуются целиком, хотя восстановить надо девять верхних строк (замер + * 2026-08-17: блит в pop_ceil_bake_empty стоит 21 447 против 14 384 в + * pop_floor_bake — ровно разница в высоте). Окно и отсеивает куски, вовсе не + * попавшие в прямоугольник (предфильтр pop_blit_b — ДО atlas_image и + * gfx_w0_map), и режет остальные по нему. + * + * ВАЖНО: пока окно стоит, pop_blit_b НЕ ставит пометку «фон трогали» (признак + * fore-прохода) — вызывающий обязан пометить прямоугольник САМ (pop_cd_touch + * или pop_bar_black, который метит сам). */ +void pop_t_win_set(int x, int ytop, int w, int h); +void pop_t_win_clear(void); + /* ---- Таблица тайлов (piece, seg008.c:27) ---------------------------- */ typedef struct { uint8_t base_id, floor_left; int8_t base_y;