Чёрные бары поверх стража: заливка запечки уезжала за правый край экрана
Найдено пользователем (2026-08-17, уровень 2 комната 4): после падения плит
(1,7) и (1,8) поверх стража в (1,0) появляются два чёрных бара, большой и
маленький, и МЕРЦАЮТ — то есть живут только на одной странице дабл-буфера.
Пока плиты не упали, баров нет.
Диагноз пользователя оказался верным дважды — и в причине, и в механизме.
ПРИЧИНА. Падение плиты (1,8) помечает соседа (1,9), и pop_floor_bake заливает
там прямоугольник ШИРИНОЙ 60 от x = 288 — то есть 348, на 28 пикселей за
экран. Ширина «свой тайл + свес соседа» (40/60/64 при шаге тайла 32) верна
для любой колонки, кроме последней.
А заливка по контракту НЕ КЛИПУЕТ, и это правильно: проверка координат стоила
бы дороже самой заливки (шапка _gfx_recthfill256: «Клиппинга НЕТ, координаты
обязаны быть валидны»). Значит обрезать обязан ВЫЗЫВАЮЩИЙ — bar не трогаем.
МЕХАНИЗМ МЕРЦАНИЯ (объяснение пользователя). Страница 1 начинается на 320
байт дальше страницы 0, поэтому запись в страницу 0 с x > 320 попадает в
страницу 1 по x−320. Отсюда и «бар только на одной странице». Хуже того,
такая же запись НА странице 1 уходит уже за пределы обеих страниц — в то, что
лежит дальше (палитры и прочее), так что баг не только косметический.
ПОДТВЕРЖДЕНИЕ АРТЕФАКТОМ. Трасса всех вызовов pop_bar_black на прогоне:
BAR x=288 y=117 w=60 h=39 ret=D754 <- pop_floor_bake, 288+60 = 348
и независимо снятое расхождение страниц: РОВНО x 0..27 (348−320 = 28 px) при
y 117..155 — то есть в точности y этого бара (yb+26 = 117, высота 39).
ФИКС. Хелпер bake_w(x, w) обрезает ширину по правому краю (и отдаёт 0, если
прямоугольник целиком за экраном); применён во всех точечных запечках —
pop_floor_bake, pop_ceil_bake_empty, pop_loose_bake_empty. Обрезка ничего не
теряет: правее 320 восстанавливать нечего. Копия второй страницы (bake_copy)
берёт ту же обрезанную ширину, иначе запечка и копия разъехались бы.
Заодно, того же рода:
- pop_torch_wipe у факела в колонке 9 заливал x = 328, то есть ЦЕЛИКОМ за
экраном — теперь пропускается;
- pop_loose_bake_empty читал POP_COL_XH[col + 1] БЕЗУСЛОВНО, то есть у
последней колонки за концом массива (10 элементов); чтение убрано под
проверку.
Отсечено по дороге (проверками, не рассуждением): копия запечки не виновата
(сборка с выключенной копией — бары остались, проверил пользователь).
8 наборов tests-host зелёные.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -686,6 +686,29 @@ void pop_room_redraw_seam_left(uint8_t rows) __banked
|
||||
}
|
||||
}
|
||||
|
||||
/* Ширина прямоугольника запечки, ОБРЕЗАННАЯ по правому краю экрана.
|
||||
*
|
||||
* Точечные запечки берут ширину «свой тайл + свес соседа» (40, 60 или 64 при
|
||||
* шаге тайла 32) — для последней колонки это уезжает за 320. А заливка и
|
||||
* копия по контракту НЕ КЛИПУЮТ (см. шапку _gfx_recthfill256: «Клиппинга НЕТ,
|
||||
* координаты обязаны быть валидны») — и правильно, что не клипуют: проверка
|
||||
* координат стоила бы дороже самой заливки. Значит обрезать обязан
|
||||
* вызывающий.
|
||||
*
|
||||
* Чем это было: падение плиты (1,8) помечает соседа (1,9), pop_floor_bake
|
||||
* заливал x=288 шириной 60, то есть 28 пикселей за экраном, и эти 28 всплывали
|
||||
* ЧЁРНОЙ ПОЛОСОЙ у левого края — поверх стража в (1,0), причём только на одной
|
||||
* странице дабл-буфера, то есть с мерцанием (найдено пользователем
|
||||
* 2026-08-17, уровень 2 комната 4; трасса заливок:
|
||||
* BAR x=288 y=117 w=60 h=39, а расхождение страниц — ровно x 0..27, y 117..155).
|
||||
*
|
||||
* Обрезка ничего не теряет: правее 320 восстанавливать нечего. */
|
||||
static uint8_t bake_w(int x, uint8_t w)
|
||||
{
|
||||
if (x >= 320) return 0; /* весь прямоугольник за экраном */
|
||||
return (x + (int)w > 320) ? (uint8_t)(320 - x) : w;
|
||||
}
|
||||
|
||||
/* ---- ЗАПЕЧКА НА ВТОРУЮ СТРАНИЦУ — копией, а не пересчётом ----------- *
|
||||
* Точечные запечки ставятся с pages = 2, то есть срабатывают ДВА кадра подряд
|
||||
* — по разу на каждую страницу дабл-буфера, — и оба раза считают одно и то же.
|
||||
@@ -709,6 +732,11 @@ static uint8_t bake_copy(uint8_t *slot, int x, int ytop, int w, int h)
|
||||
{
|
||||
uint8_t pg = (uint8_t)(gfx_get_draw_page() & 1);
|
||||
gfx_rect_t r;
|
||||
#ifdef POP_DBG_NO_BAKE_COPY
|
||||
(void)x; (void)ytop; (void)w; (void)h; /* A/B: копия выключена целиком */
|
||||
*slot = (uint8_t)(pg + 1);
|
||||
return 0;
|
||||
#endif
|
||||
if (!*slot || (uint8_t)(*slot - 1) == pg ||
|
||||
gfx_get_draw_page() == gfx_get_visible_page()) {
|
||||
*slot = (uint8_t)(pg + 1); /* это ПЕРВАЯ запечка */
|
||||
@@ -788,19 +816,20 @@ void pop_ceil_shake_draw(int col) __banked
|
||||
void pop_ceil_bake_empty(int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
uint8_t bw = bake_w(x, 64); /* обрезка по краю экрана — см. bake_w */
|
||||
/* Вторая страница — копией с первой (см. bake_copy). */
|
||||
if (bake_copy(&bake_pg_above[col], x, POP_YOFF, 64, CEIL_BAND_H + 1)) return;
|
||||
if (bake_copy(&bake_pg_above[col], x, POP_YOFF, bw, CEIL_BAND_H + 1)) return;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* Отдельного pop_cd_touch тут не надо: pop_bar_black метит сам, и ровно
|
||||
* этот же прямоугольник (было два вызова на одну область). */
|
||||
pop_bar_black(x, POP_YOFF, 64, CEIL_BAND_H + 1); /* стереть плиту в ОЗУ-копии */
|
||||
if (bw) pop_bar_black(x, POP_YOFF, bw, 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_win_set(x, POP_YOFF, bw, CEIL_BAND_H + 1);
|
||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||||
@@ -907,9 +936,12 @@ void pop_torch_wipe(int row, int col) __banked
|
||||
{
|
||||
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||||
int yb = POP_YOFF + 63 * row + 62 - 40;
|
||||
/* У факела в колонке 9 канвас пламени лежит на x = 328, то есть ЦЕЛИКОМ за
|
||||
* правым краем — заливать нечего, а bar не клипует (см. bake_w). */
|
||||
uint8_t tw = bake_w(x, 16);
|
||||
pop_cd_touch(x, yb - 17, 16, 18); /* bar мимо pop_blit_b */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_bar_black(x, yb - 17, 16, 18);
|
||||
if (tw) pop_bar_black(x, yb - 17, tw, 18);
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
pop_t_bake_rest = 1; /* фон = покой */
|
||||
draw_tile(row, col);
|
||||
@@ -926,15 +958,16 @@ void pop_floor_bake(int row, int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
int yb = POP_YOFF + 63 * row;
|
||||
uint8_t bw = bake_w(x, 60); /* обрезка по краю экрана — см. bake_w */
|
||||
/* Вторая страница — копией с первой (см. bake_copy). */
|
||||
if (bake_copy(&bake_pg[row * 10 + col], x, yb + 26, 60, 39)) return;
|
||||
if (bake_copy(&bake_pg[row * 10 + col], x, yb + 26, bw, 39)) return;
|
||||
/* Пометку ставит сам 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);
|
||||
if (bw) pop_bar_black(x, yb + 26, bw, 39);
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
/* ОКНО КЛИПА ЗДЕСЬ ОБЯЗАТЕЛЬНО — и не ради скорости.
|
||||
*
|
||||
@@ -954,7 +987,7 @@ void pop_floor_bake(int row, int col) __banked
|
||||
*
|
||||
* Что бар покрывает ВСЁ изменившееся — это инвариант самого бара (иначе
|
||||
* запечка не работала бы и без копии). */
|
||||
pop_t_win_set(x, yb + 26, 60, 39);
|
||||
pop_t_win_set(x, yb + 26, bw, 39);
|
||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||
draw_tile(row, col);
|
||||
if (col + 1 < 10) draw_tile(row, col + 1);
|
||||
@@ -1012,7 +1045,9 @@ void pop_gate_redraw(int row, int col) __banked
|
||||
void pop_loose_bake_empty(int row, int col) __banked
|
||||
{
|
||||
int x = POP_COL_XH[col] * 8;
|
||||
int xr = POP_COL_XH[col + 1] * 8;
|
||||
/* col+1 читаем ТОЛЬКО в границах массива: у последней колонки соседа нет,
|
||||
* а POP_COL_XH[10] — чтение за концом (10 элементов). */
|
||||
int xr = (col + 1 < 10) ? POP_COL_XH[col + 1] * 8 : 0;
|
||||
int yb = 63 * row + 46;
|
||||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
||||
@@ -1030,9 +1065,9 @@ void pop_loose_bake_empty(int row, int col) __banked
|
||||
pop_cd_touch(x, yb, 72, POP_YOFF + 63 * row + 66 - yb);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
pop_bar_black(x, yb, 40, POP_YOFF + 63 * row + 65 - yb + 1); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||||
if (bake_w(x, 40)) pop_bar_black(x, yb, bake_w(x, 40), POP_YOFF + 63 * row + 65 - yb + 1); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||||
if (col + 1 < 10)
|
||||
pop_bar_black(xr, yb, 32, POP_YOFF + 63 * row + 65 - yb + 1); /* сосед: РОВНО 32 — не залезть в col+2 */
|
||||
if (bake_w(xr, 32)) pop_bar_black(xr, yb, bake_w(xr, 32), POP_YOFF + 63 * row + 65 - yb + 1); /* сосед: РОВНО 32 — не залезть в col+2 */
|
||||
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||||
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
||||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||||
|
||||
Reference in New Issue
Block a user