Падающие плиты: спрайты падающего набора, замеры слоёв, анализ midtable

Спрайты: у падающего куска obj_id = 10 (add_mob_to_objtable, seg007:1170),
и три части берутся из таблиц по этому индексу — 70/74/72, а не 41/43/42.
Мы рисовали плиту В ПОКОЕ, отсюда «цельная ровная» вместо двух половин со
сдвигом правой на пиксель.

Подпорка «перерисовать соседний тайл поверх куска» убрана: она тащила на
плиту чужой узор и окно, да ещё стоила по одной полной отрисовке тайла на
кусок за кадр.  Клип объекта (clip.right = 40) НЕ портирован — единицы поля
не выяснены, буквальные 40 пикселей срезают задний угол плиты.  Цена отказа
— правая грань куска и передние части чужих тайлов его не перекрывают
(MOB-CLIP-RIGHT, BUGS_OPEN.md).

Коридор heal сужен по высоте 32 -> 24: реальный след спрайта 16 px.
Ширина оставлена 64 — из неё используются 62, экономить нечего; заодно в
комментарии зафиксировано, что «выравнивание блока акселератора» ничем не
подтверждается и требует проверки артефактом.

docs/midtable_analysis.md — разбор слоёв оригинала и замеры:
- логический кадр 1 289 526 тактов;
- pop_redraw_needed 978 тактов (0,08 %);
- fore-проход 128 778 тактов (10 %) на ОДНОГО персонажа, но выполняется
  лишь на 4 % кадров — гасит пропуск неизменившегося персонажа;
- максимум объектов на одном тайле 2 (пара из loose_fall), значит
  сортировка внутри тайла бесплатна.

Вывод: единственный риск порта objtable — сохранится ли окно клипа
fore-прохода; до разбора redraw_frames_fore выбирать вариант рано.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 19:56:56 +03:00
parent 67d62e3e8b
commit 9e1a55bdc3
5 changed files with 270 additions and 50 deletions
+40 -50
View File
@@ -938,7 +938,22 @@ void pop_loose_mob_spawn_at(uint8_t room, int row, int col) __banked
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
#define MOB_X0(mx) ((mx) - 4)
#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */
/* Коридор 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 не
* должно утечь в новую комнату). */
@@ -1031,7 +1046,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
* ЗАДЕЛ: правильнее не перерисовывать соседа вовсе, а КЛИПОВАТЬ
* правую часть куска — оригинал так и делает (add_mob_to_objtable,
* seg007:1161: clip.right = 40). Тогда коридор вернётся к 64x32. */
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - MOB_HEAL_UP, MOB_W, MOB_HEAL_H);
m->prev_y[pg] = MOB_Y_NONE;
}
if (!m->active) { m->clean--; return; }
@@ -1194,55 +1209,30 @@ static void mob_render(mob_t *m, uint8_t pg)
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
* заходит в тайл СПРАВА) на obj_y-1. */
gfx_set_bank(GFX_BANK_SPRITE);
pop_env_b(43, m->x, m->y);
pop_env_b(41, m->x, m->y - 3);
pop_env_b(42, m->x + 32, m->y - 1);
/* КЛИПА ОБЪЕКТА ЗДЕСЬ НЕТ. У оригинала он есть — add_mob_to_objtable
* (seg007:1161) ставит куску clip.right = 40, — но единицы этого поля я
* НЕ выяснил: буквальные 40 экранных пикселей срезают правый задний угол
* плиты (прогон 2026-08-13). У оригинала obj_x живёт в удвоенных
* единицах (char_x_left = obj_x / 2 + 58), так что 40 может означать и
* не пиксели. До выяснения рисуем кусок целиком.
*
* Подпорки «перерисовать соседний тайл поверх куска» тоже нет: она тащила
* на плиту чужой узор и окно (MOB-CLIP-RIGHT). Цена отказа — правая
* грань куска не прячется за передней гранью соседнего пола; артефакт
* мельче прежнего и уйдёт вместе с сортируемым midtable. */
/* Спрайты ПАДАЮЩЕГО куска, а не лежащей плиты. У объекта в objtable
* obj_id = 10 (add_mob_to_objtable, seg007:1170), и три части берутся из
* таблиц по этому индексу (seg008:518/596/608):
* loose_fram_left[10] = 70 (32x13, y-3)
* loose_fram_bottom[10] = 74 (32x3, y)
* loose_fram_right[10] = 72 (26x16, x+32, y-1)
* Здесь стояли 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);
m->prev_y[pg] = m->y;
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
* до heal'а следующего кадра — виден как хвост во время падения). */
if (m->col + 1 < 10) {
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
* через пол + перекрыта передняя грань пола.
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
* границе рядов. Порт draw_mob (seg007:13E5). */
int8_t r = pop_y_to_row((int16_t)m->y);
int8_t rt = pop_y_to_row((int16_t)(m->y - 18));
/* ОКНО = КОРИДОР HEAL ЭТОГО КУСКА, ни пикселем шире. draw_tile
* рисует тайл ЦЕЛИКОМ (пол, грани, верх кладки ряда ниже — порядка
* 64x63), причём в GFX_BANK_SPRITE, то есть мимо теневой копии фона.
* Всё, что он клал за пределами коридора, не стиралось НИКОГДА:
* кусок улетал, а в соседнем столбце оставался обрезок пола
* (найдено прогоном 2026-08-13).
*
* Расширять под это heal НЕЛЬЗЯ: heal — самая дорогая операция
* кадра, а на 13-м уровне таких кусков одновременно шесть. Поэтому
* режем не heal под перерисовку, а перерисовку под heal — тем же
* окном, которым fore-слой отсекает лишние тайлы. */
pop_t_fclip_x0 = MOB_X0(m->x);
pop_t_fclip_x1 = MOB_X0(m->x) + MOB_W;
pop_t_fclip_y0 = m->y - 27 + POP_YOFF;
pop_t_fclip_y1 = pop_t_fclip_y0 + 32;
pop_t_fclip_ly0 = pop_t_fclip_y0; /* переворота у кусков нет */
pop_t_fclip_ly1 = pop_t_fclip_y1;
pop_t_fclip_on = 1;
if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */
draw_tile(r, m->col + 1);
pop_t_clip_top = 0;
if (rt != r) {
if (rt < 0) pop_t_clip_top = POP_YOFF;
draw_tile(rt, m->col + 1);
pop_t_clip_top = 0;
}
pop_t_fclip_on = 0;
}
gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);