Чёрные бары поверх стража: заливка запечки уезжала за правый край экрана
Найдено пользователем (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, то есть срабатывают ДВА кадра подряд
|
* Точечные запечки ставятся с 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);
|
uint8_t pg = (uint8_t)(gfx_get_draw_page() & 1);
|
||||||
gfx_rect_t r;
|
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 ||
|
if (!*slot || (uint8_t)(*slot - 1) == pg ||
|
||||||
gfx_get_draw_page() == gfx_get_visible_page()) {
|
gfx_get_draw_page() == gfx_get_visible_page()) {
|
||||||
*slot = (uint8_t)(pg + 1); /* это ПЕРВАЯ запечка */
|
*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
|
void pop_ceil_bake_empty(int col) __banked
|
||||||
{
|
{
|
||||||
int x = POP_COL_XH[col] * 8;
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
uint8_t bw = bake_w(x, 64); /* обрезка по краю экрана — см. bake_w */
|
||||||
/* Вторая страница — копией с первой (см. bake_copy). */
|
/* Вторая страница — копией с первой (см. 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);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
/* Отдельного pop_cd_touch тут не надо: pop_bar_black метит сам, и ровно
|
/* Отдельного 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);
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
pop_t_clip_top = POP_YOFF;
|
pop_t_clip_top = POP_YOFF;
|
||||||
/* Окно = ровно стёртая полоса. Главный выигрыш здесь не в отсеве, а в
|
/* Окно = ровно стёртая полоса. Главный выигрыш здесь не в отсеве, а в
|
||||||
* КЛИПЕ: куски ряда 0 высотой 63 px рисовались целиком (клип был только
|
* КЛИПЕ: куски ряда 0 высотой 63 px рисовались целиком (клип был только
|
||||||
* сверху, по pop_t_clip_top), хотя восстановить надо девять строк —
|
* сверху, по pop_t_clip_top), хотя восстановить надо девять строк —
|
||||||
* отсюда 21 447 такта на блит против 14 384 в pop_floor_bake. */
|
* отсюда 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) */
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||||||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
* два тайла ряда −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 x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||||||
int yb = POP_YOFF + 63 * row + 62 - 40;
|
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 */
|
pop_cd_touch(x, yb - 17, 16, 18); /* bar мимо pop_blit_b */
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
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);
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
pop_t_bake_rest = 1; /* фон = покой */
|
pop_t_bake_rest = 1; /* фон = покой */
|
||||||
draw_tile(row, col);
|
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 x = POP_COL_XH[col] * 8;
|
||||||
int yb = POP_YOFF + 63 * row;
|
int yb = POP_YOFF + 63 * row;
|
||||||
|
uint8_t bw = bake_w(x, 60); /* обрезка по краю экрана — см. bake_w */
|
||||||
/* Вторая страница — копией с первой (см. bake_copy). */
|
/* Вторая страница — копией с первой (см. 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_bar_black — по тому же прямоугольнику; отдельный
|
||||||
* pop_cd_touch тут был вторым вызовом на одну область (4 502 такта). */
|
* pop_cd_touch тут был вторым вызовом на одну область (4 502 такта). */
|
||||||
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
||||||
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
||||||
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
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);
|
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) */
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
draw_tile(row, col);
|
draw_tile(row, col);
|
||||||
if (col + 1 < 10) draw_tile(row, col + 1);
|
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
|
void pop_loose_bake_empty(int row, int col) __banked
|
||||||
{
|
{
|
||||||
int x = POP_COL_XH[col] * 8;
|
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;
|
int yb = 63 * row + 46;
|
||||||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||||||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель 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);
|
pop_cd_touch(x, yb, 72, POP_YOFF + 63 * row + 66 - yb);
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
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)
|
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 комнаты
|
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||||||
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
||||||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||||||
|
|||||||
Reference in New Issue
Block a user