From af189a1d927603bf657cd364244eb87afc6af770 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 17 Aug 2026 14:20:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BF=D0=BE=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=20=D0=BA=D1=83=D1=81=D0=BA=D0=B0:=20=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=B3=D0=B0=D0=B1=D0=B0=D1=80=D0=B8=D1=82?= =?UTF-8?q?=20+=20=D0=98=D0=A1=D0=9F=D0=A0=D0=90=D0=92=D0=9B=D0=95=D0=9D?= =?UTF-8?q?=D0=98=D0=95=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=B2=20b2da0b8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ПОПРАВКА К b2da0b8. Там я снял размеры частей падающего куска из каталогов атласов с НЕВЕРНЫМ сдвигом раскладки (page = id>>5 вместо id>>4 — POP_ENV_SHIFT равен 4). Настоящие размеры: оба тайлсета 70 = 32x13 74 = 32x3 72 = 26x16 в подземелье, 25x16 во дворце То есть прежний комментарий в mob_render (32x13 / 32x3 / 26x16) был ВЕРЕН, а «исправление» — нет. Следствия: - НИКАКОГО БАГА КОРИДОРА НЕ БЫЛО: след куска по x — mob_x .. mob_x+57, и старый коридор mob_x-4 .. mob_x+59 его покрывал. Заявление про «три пикселя, не стиравшиеся во дворце» неверно, снимаю. - Сам коридор оставляю как стало (mob_x .. mob_x+63): площадь та же, но четыре пикселя запаса переехали слева, где кусок не рисует ничего, вправо, где их было всего два. Это не исправление бага, а перекладка запаса. - КОД во всех случаях работал правильно: он читает раскладку через POP_ENV_SHIFT/POP_ENV_MASK, ошибка была только в моём анализе. Само дело: композит собирается с ТОЧНЫМ габаритом вместо буфера-максимума. Габариты частей читаются первым проходом (у тайлсетов правая часть разная), из них считаются ширина и высота блоба, и страйд равен ширине. Раньше блоб объявлялся 63 px шириной при фактических 58 — пять прозрачных колонок переносились на каждом кадре каждого куска. циан пик 273 108 -> 270 510 работа пик 920 862 -> 916 458 Проверено: 8 наборов tests-host зелёные; кадр с четырьмя плитами в воздухе совпадает пиксельно с прежним. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/pop_room.c | 163 +++++++++++++-------------- 1 file changed, 78 insertions(+), 85 deletions(-) diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index aac89f6..013badd 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -111,81 +111,74 @@ static int bg_load_tile_pal(const char *name) static uint8_t mob_heal_up = 20; static uint8_t mob_heal_h = 24; -#define MOBS_MAXW 63 -#define MOBS_MAXH 19 +/* Размер буфера — верхняя граница по обоим тайлсетам с запасом; фактические + * w/h композита считаются при сборке и кладутся в его шапку. */ +#define MOBS_MAXW 64 +#define MOBS_MAXH 20 static uint8_t mob_spr[4 + MOBS_MAXW * MOBS_MAXH]; static uint8_t mob_spr_ok; /* 0 — не собрался, рисуем тремя блитами */ -/* Одна часть в композит. xo — смещение по x, ybo — смещение НИЗА части - * относительно низа композита (0 = вплотную к низу, отрицательное = выше). - * top_off — смещение верхней строки композита относительно mob_y. */ -static uint8_t mob_spr_put(uint8_t id, uint8_t xo, int8_t ybo, int8_t top_off) -{ - atlas_t *a = &pop_env[id >> POP_ENV_SHIFT]; - uint8_t idx = (uint8_t)(id & POP_ENV_MASK); - const uint8_t *src; - uint8_t w, h, r, c, v; - int row0; - if (idx >= a->count) return 0; - src = (const uint8_t *)atlas_image(a, idx); - gfx_w0_map(a->page); - w = src[0]; h = src[2]; - if (src[1] || src[3] || !w || !h || - (uint16_t)xo + w > MOBS_MAXW) { gfx_w0_unmap(); return 0; } - row0 = (int)ybo - (int)h + 1 - (int)top_off; /* верхняя строка части */ - if (row0 < 0 || row0 + h > MOBS_MAXH) { gfx_w0_unmap(); return 0; } - src += 4; - for (r = 0; r < h; r++) { - const uint8_t *sp = src + (uint16_t)r * w; - uint8_t *dp = mob_spr + 4 + (uint16_t)(row0 + r) * MOBS_MAXW + xo; - for (c = 0; c < w; c++) { v = sp[c]; if (v != 0xFF) dp[c] = v; } - } - gfx_w0_unmap(); - return 1; -} - -/* Высота композита: самый верхний пиксель даёт часть 70 (ybo −3), у неё же - * самая большая высота. Считаем по фактическим габаритам, а не по константам - * — они у тайлсетов разные. */ -static int8_t mob_spr_top_off(void) -{ - static const uint8_t ids[3] = {74, 70, 72}; - static const int8_t ybo[3] = {0, -3, -1}; - int8_t best = 0; - uint8_t k; - for (k = 0; k < 3; k++) { - atlas_t *a = &pop_env[ids[k] >> POP_ENV_SHIFT]; - uint8_t idx = (uint8_t)(ids[k] & POP_ENV_MASK); - const uint8_t *src; - int t; - if (idx >= a->count) continue; - src = (const uint8_t *)atlas_image(a, idx); - gfx_w0_map(a->page); - t = (int)ybo[k] - (int)src[2] + 1; - gfx_w0_unmap(); - if (t < best) best = (int8_t)t; - } - return best; -} +/* Части композита: id, смещение по x, смещение НИЗА части относительно mob_y. + * Порядок — как в mob_render (низ, левая часть, правая), он важен: части + * перекрываются. */ +static const uint8_t MOBS_ID[3] = {74, 70, 72}; +static const uint8_t MOBS_XO[3] = {0, 0, 32}; +static const int8_t MOBS_YBO[3] = {0, -3, -1}; static void mob_spr_build(void) { - uint16_t i; - int8_t top_off; + uint8_t w[3], h[3], k, r, c, v, cw, ch; + const uint8_t *src; + int top = 0, row0; + mob_spr_ok = 0; - top_off = mob_spr_top_off(); - if (top_off <= -MOBS_MAXH) return; /* не влезло — общий путь */ - mob_spr[0] = MOBS_MAXW; mob_spr[1] = 0; - mob_spr[2] = (uint8_t)(1 - top_off); /* низ композита = mob_y */ - mob_spr[3] = 0; - for (i = 0; i < (uint16_t)MOBS_MAXW * MOBS_MAXH; i++) mob_spr[4 + i] = 0xFF; - /* Порядок как в mob_render: низ, левая часть, правая часть. */ - if (!mob_spr_put(74, 0, 0, top_off)) return; - if (!mob_spr_put(70, 0, -3, top_off)) return; - if (!mob_spr_put(72, 32, -1, top_off)) return; + /* Проход 1 — габариты частей. Считать их обязательно, а не брать + * константами: у тайлсетов они РАЗНЫЕ (напр. правая часть 72 — 26 px в + * подземелье и 25 во дворце). */ + for (k = 0; k < 3; k++) { + atlas_t *a = &pop_env[MOBS_ID[k] >> POP_ENV_SHIFT]; + uint8_t idx = (uint8_t)(MOBS_ID[k] & POP_ENV_MASK); + if (idx >= a->count) return; + src = (const uint8_t *)atlas_image(a, idx); + gfx_w0_map(a->page); + w[k] = src[0]; h[k] = src[2]; + v = (uint8_t)(src[1] | src[3]); + gfx_w0_unmap(); + if (v || !w[k] || !h[k]) return; /* кадр >255 или пустой */ + row0 = (int)MOBS_YBO[k] - (int)h[k] + 1; + if (row0 < top) top = row0; + } + /* Габарит композита: низ = mob_y, верх = самая высокая часть. */ + ch = (uint8_t)(1 - top); + cw = 0; + for (k = 0; k < 3; k++) + if ((uint8_t)(MOBS_XO[k] + w[k]) > cw) cw = (uint8_t)(MOBS_XO[k] + w[k]); + if (cw > MOBS_MAXW || ch > MOBS_MAXH) return; + mob_spr[0] = cw; mob_spr[1] = 0; + mob_spr[2] = ch; mob_spr[3] = 0; + /* Пустое место — ПРОЗРАЧНОЕ (0xFF), иначе композит закрасил бы фон вокруг + * частей. Страйд блоба = его ширина, поэтому чистим ровно cw*ch. */ + for (r = 0; r < ch; r++) + for (c = 0; c < cw; c++) mob_spr[4 + (uint16_t)r * cw + c] = 0xFF; + /* Проход 2 — копирование ПОПИКСЕЛЬНО с пропуском 0xFF: части + * ПЕРЕКРЫВАЮТСЯ (74 и 70 обе идут от mob_x), и прозрачные пиксели верхней + * не должны стирать нижнюю — ровно как три прозрачных блита друг поверх + * друга. */ + for (k = 0; k < 3; k++) { + atlas_t *a = &pop_env[MOBS_ID[k] >> POP_ENV_SHIFT]; + src = (const uint8_t *)atlas_image(a, (uint8_t)(MOBS_ID[k] & POP_ENV_MASK)); + gfx_w0_map(a->page); + row0 = (int)MOBS_YBO[k] - (int)h[k] + 1 - top; + for (r = 0; r < h[k]; r++) { + const uint8_t *sp = src + 4 + (uint16_t)r * w[k]; + uint8_t *dp = mob_spr + 4 + (uint16_t)(row0 + r) * cw + MOBS_XO[k]; + for (c = 0; c < w[k]; c++) { v = sp[c]; if (v != 0xFF) dp[c] = v; } + } + gfx_w0_unmap(); + } /* Коридор heal — по композиту: две строки запаса сверху, одна снизу. */ - mob_heal_up = (uint8_t)(mob_spr[2] + 1); - mob_heal_h = (uint8_t)(mob_spr[2] + 3); + mob_heal_up = (uint8_t)(ch + 1); + mob_heal_h = (uint8_t)(ch + 3); mob_spr_ok = 1; } @@ -1040,25 +1033,24 @@ static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25}; /* Габарит падающего куска. Части и их места (порт draw_mob, seg007:13E5; * индексы loose_fram_*[10], seg008:518/596/608): * - * env 70 левая часть при mob_y-3 - * env 74 низ при mob_y - * env 72 правая часть при mob_x+32, mob_y-1 + * env 70 левая часть при (mob_x, mob_y-3) + * env 74 низ при (mob_x, mob_y) + * env 72 правая часть при (mob_x+32, mob_y-1) * - * РАЗМЕРЫ У ТАЙЛСЕТОВ РАЗНЫЕ (снято из каталогов атласов 2026-08-17; прежний - * комментарий «41=32x13, 42=26x15, 43=32x3» был неверен вдвойне — и id, и - * размеры): + * Размеры (сняты из каталогов атласов; РАСКЛАДКА env: page = id>>4, + * idx = id&15 — POP_ENV_SHIFT равен 4, не 5): * - * подземелье 70 = 32x16 74 = 26x15 72 = 26x16 - * дворец 70 = 32x13 74 = 25x15 72 = 31x13 + * оба тайлсета 70 = 32x13 74 = 32x3 + * 72 = 26x16 в подземелье, 25x16 во дворце * - * Отсюда фактический след: по x mob_x .. mob_x+62 (дворец: 32+31), - * по y mob_y-18 .. mob_y (подземелье: 3+16-1). + * Отсюда след: по x mob_x .. mob_x+57 (32+26−1), по y mob_y-16 .. mob_y + * (правая часть выше всех: 1+16−1). Именно эти числа и стоят в шапке + * mob_render — composite их пересчитывает сам и кладёт в шапку блоба. * - * КОРИДОР ИСПРАВЛЕН 2026-08-17: было MOB_X0 = mob_x-4 при MOB_W = 64, то есть - * mob_x-4 .. mob_x+59 — на ДВОРЦОВЫХ уровнях (10, 11, 13, 14) правые три - * пикселя куска 72 не попадали в heal и оставались висеть на странице. - * Четыре пикселя слева при этом чистились впустую: левее mob_x кусок не - * рисует ничего. */ + * Коридор: MOB_X0 = mob_x при MOB_W = 64 даёт mob_x .. mob_x+63, то есть + * шесть пикселей запаса справа. Раньше было mob_x-4 при той же ширине + * (mob_x-4 .. mob_x+59): четыре пикселя слева, где кусок не рисует ничего, + * и всего два справа. Площадь та же, запас переехал туда, где он нужен. */ #define MOB_X0(mx) (mx) #define MOB_W 64 /* mob_x .. mob_x+63 */ /* Коридор heal по высоте: mob_y-20 .. mob_y+3 при следе mob_y-18 .. mob_y. @@ -1484,7 +1476,8 @@ static void mob_render(mob_t *m, uint8_t pg) * Здесь стояли 41/43/42 — это индекс 0, то есть плита В ПОКОЕ. Отсюда и * «цельная ровная плита» вместо двух частей со сдвигом правой половины * на пиксель (замечено пользователем 2026-08-13 сравнением с оригиналом). - * Габариты частей — в блоке MOB_X0 выше (у тайлсетов они РАЗНЫЕ). + * Габариты частей — в блоке MOB_X0 выше (правая часть у тайлсетов + * РАЗНАЯ: 26 px в подземелье, 25 во дворце). * * Рисуем ОДНИМ блитом по композиту (mob_spr_build): три вызова блита * стоили 258 708 такта в кадре при шести кусках — больше половины @@ -1494,7 +1487,7 @@ static void mob_render(mob_t *m, uint8_t pg) * (MOB_X0 .. +MOB_W, y−27 .. +37) уже помечен ОДНИМ вызовом — из * pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска, * рождённого внутри тика. Спрайт целиком внутри коридора: по x он - * занимает m->x .. m->x+62, по y m->y−18 .. m->y, коридор — m->x .. + * занимает m->x .. m->x+57, по y m->y−16 .. m->y, коридор — m->x .. * m->x+63 и m->y−27 .. m->y+37 (запас на ускорение, speed ≤ 31 за кадр). * Замер 2026-08-17: три настоящих pop_cd_touch стоили 13 506 тактов на * кусок, то есть 81 000 в кадре при шести кусках. */ @@ -1510,7 +1503,7 @@ static void mob_render(mob_t *m, uint8_t pg) * Окно ставим только когда кусок реально задевает борт: внутри поля блиты * идут быстрым путём (10 422 против 14 088). */ { - int ytop = m->y - 18 + POP_YOFF, ybot = m->y + POP_YOFF; + 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); if (mob_spr_ok) {