Композит куска: точный габарит + ИСПРАВЛЕНИЕ размеров в b2da0b8

ПОПРАВКА К 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 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 14:20:29 +03:00
parent 9a50ab20d7
commit af189a1d92
+78 -85
View File
@@ -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+261), по y mob_y-16 .. mob_y
* (правая часть выше всех: 1+161). Именно эти числа и стоят в шапке
* 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, y27 .. +37) уже помечен ОДНИМ вызовом — из
* pop_loose_mob_tick для летящих кусков и из mob_spawn_copy для куска,
* рождённого внутри тика. Спрайт целиком внутри коридора: по x он
* занимает m->x .. m->x+62, по y m->y18 .. m->y, коридор — m->x ..
* занимает m->x .. m->x+57, по y m->y16 .. m->y, коридор — m->x ..
* m->x+63 и m->y27 .. 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) {