Падающие плиты: передний слой по пометкам, честные спрайты, замер кадра

Слой fore переведён на пометки ОБЪЕКТОВ вместо окна вокруг персонажа —
порт set_redraw_fore/redraw_frames_fore (seg007:0550, seg008:214):
pop_set_redraw_fore + pop_fore_needed + pop_fore_tile_b, пометки ставит
падающий кусок вокруг себя (draw_mob, seg007:1147).

Ключевая правка: пометки соседа считаются в ПРОХОДЕ ОТРИСОВКИ, а не в
тике.  Стояли в цикле тика — то есть по позиции ДО mob_tick_one, а кусок
рисовался уже по новой; пока он летит внутри ряда, тайлы совпадают, но в
кадр пересечения границы пометка указывала на прежний тайл и колонна кусок
не перекрывала.  Ровно один кадр, как и наблюдалось.

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

Убрана подпорка «перерисовать соседний тайл поверх куска»: тащила на плиту
чужой узор и окно и стоила по полной отрисовке тайла на кусок за кадр.
Коридор heal сужен по высоте 32 -> 24 (реальный след спрайта 16 px).
gfx_set_bank вынесен из потайлового цикла в обрамление прохода.

Выяснено и зафиксировано: clip.right = 40 в add_mob_to_objtable — МЁРТВЫЕ
данные.  set_clip_rect вызывается под chtab_flip_clip[chtab_id], а для
окружения флаг равен нулю — оригинал падающие куски не клипует вовсе.
Портировать нечего; две попытки это сделать были ошибочны.

Замер кадра (маркеры m1..m4, оставлены временно для проверки правок):
покой 196 614 тактов (15,2 % кадра при периоде 1 290 000), во время
падения гряды среднее 222 667, пик 1 428 990 — то есть 111 % кадра,
шесть кадров подряд за пределом.  Логика неизменна (88 020), растут фон
(до 887 376) и спрайты (до 660 792).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 21:38:59 +03:00
parent c31930dcad
commit 50e4eda2ad
8 changed files with 123 additions and 1 deletions
+18
View File
@@ -889,6 +889,24 @@ static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер
* кадра; всё остальное берётся из самого персонажа (ch). Окно fore-клипа к
* этому моменту уже стоит: его задаёт тот, кто нарисовал спрайт. */
/* Передний слой ОДНОГО тайла — вход для прохода по пометкам
* (pop_fore_needed). Без окна fore-клипа: пометка уже сузила работу до
* нужных тайлов, а кусок тайла обязан лечь целиком — у оригинала foretable
* ничем не режется. */
void pop_fore_tile_b(int row, int col) __banked
{
uint8_t saved = pop_t_fclip_on;
pop_t_fclip_on = 0;
fore_tile(row, col);
pop_t_fclip_on = saved;
}
/* Банк на ВЕСЬ проход пометок, а не на каждый тайл: помеченных тайлов за
* кадр бывает больше десятка, и две смены банка на каждый — заметная доля
* прохода, который сам по себе дешёвый. */
void pop_fore_pass_begin(void) __banked { gfx_set_bank(GFX_BANK_SPRITE); }
void pop_fore_pass_end(void) __banked { gfx_set_bank(GFX_BANK_NORMAL); }
void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y,
uint16_t w, uint16_t h) __banked
{
+5
View File
@@ -176,6 +176,11 @@ void pop_loose_mob_room_changed(void) __banked;
* seg008:1561). Звать после отрисовки персонажей, перед fore-слоем. */
/* Отрисовка падающих кусков, идущих ПОД Kid. Звать ПОСЛЕ pop_redraw_needed()
* — фон кадра обязан быть готов до любого спрайта. */
/* Передний слой ОДНОГО тайла — зовёт pop_fore_needed (pop_redraw.c). */
void pop_fore_tile_b(int row, int col) __banked;
void pop_fore_pass_begin(void) __banked;
void pop_fore_pass_end(void) __banked;
void pop_loose_mob_draw(void) __banked;
void pop_loose_mob_draw_over(void) __banked;
+29
View File
@@ -18,6 +18,10 @@ static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS];
/* Сколько всего непустых пометок: без него проход по 40 ячейкам крутился бы
* КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */
static uint8_t rd_pending;
/* Пометки ПЕРЕДНЕГО слоя — однокадровые, отдельно от rd_*: те живут по
* страницам дабл-буфера (запекают фон), а эти транзиентны. */
static uint8_t rdf[NTILES];
static uint8_t rdf_pending;
void pop_redraw_reset(void) __banked
{
@@ -25,6 +29,31 @@ void pop_redraw_reset(void) __banked
for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; }
for (i = 0; i < NCOLS; i++) { rda_kind[i] = 0; rda_cnt[i] = 0; }
rd_pending = 0;
for (i = 0; i < NTILES; i++) rdf[i] = 0;
pop_fore_pass_end();
rdf_pending = 0;
}
void pop_set_redraw_fore(uint8_t tilepos) __banked
{
if (tilepos >= NTILES) return;
if (!rdf[tilepos]) rdf_pending++;
rdf[tilepos] = 1;
}
/* Разбор пометок переднего слоя: тайлы обходятся в том же порядке, что и
* фон, и рисуются ПОСЛЕ всех объектов кадра — как foretable у оригинала
* (draw_tables, seg008:1373: back -> mid -> fore). */
void pop_fore_needed(void) __banked
{
uint8_t i, row = 0, col = 0;
if (!rdf_pending) return;
pop_fore_pass_begin();
for (i = 0; i < NTILES; i++) {
if (rdf[i]) { rdf[i] = 0; pop_fore_tile_b(row, col); }
if (++col == 10) { col = 0; row++; }
}
rdf_pending = 0;
}
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked
+11
View File
@@ -40,6 +40,17 @@
* дабл-буфера перерисовать (1 = только текущая, 2 = обе). Повторная
* пометка перекрывает вид и берёт максимум счётчика. */
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages) __banked;
/* ПЕРЕДНИЙ СЛОЙ по пометкам — порт redraw_frames_fore/set_redraw_fore
* (seg007:0550). В оригинале эти пометки ставят ОБЪЕКТЫ, каждый вокруг
* себя: персонаж — прямоугольник футпринта (redraw_at_char, seg003:0576),
* падающий кусок — соседа справа и второй тайл на границе рядов (draw_mob,
* seg007:1147), анимация тайла — свой тайл (draw_trob). Пометка
* ОДНОКАДРОВАЯ: передний слой транзиентен, его снимает heal.
*
* Разбор (pop_fore_needed) звать ПОСЛЕ отрисовки всех объектов кадра. */
void pop_set_redraw_fore(uint8_t tilepos) __banked;
void pop_fore_needed(void) __banked;
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked;
/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и
+37 -1
View File
@@ -21,6 +21,7 @@
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
#include "pop_state.h"
#include "pop_redraw.h" /* пометки тайлов: сосед падающего куска (draw_mob) */
#include "pop_geom.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
@@ -1165,6 +1166,30 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
*
* Порядок «кусок против Кида» здесь ни при чём — его решает defer выше, и там
* действует обычное правило. */
/* Пометки СОСЕДА СПРАВА — порт draw_mob (seg007:1147). Оригинал каждый
* кадр помечает тайл col+1 в ряду куска сразу двумя пометками:
*
* set_redraw2(tilepos, 1); // фон соседа — рисуется ДО куска
* set_redraw_fore(tilepos, 1); // его передние части — ПОСЛЕ куска
*
* и повторяет их для второго ряда, если кусок висит на границе (ypos-18).
* Так правая часть куска (спрайт 72 лежит в столбце col+1) прячется за
* передней гранью соседнего пола, а передние части чужого тайла — колонна,
* например, — ложатся поверх куска.
*
* Здесь была подпорка: целый соседний тайл рисовался ПОВЕРХ куска прямо в
* mob_render. Она тащила на плиту чужой узор и окно и стоила по полной
* отрисовке тайла на кусок за кадр (MOB-CLIP-RIGHT). */
static void mob_mark_neighbour(const mob_t *m)
{
int8_t r = pop_y_to_row((int16_t)m->y);
int8_t rt = pop_y_to_row((int16_t)(m->y - 18));
int8_t c = (int8_t)(m->col + 1);
if (c > 9) return;
if (r >= 0 && r <= 2) pop_set_redraw_fore((uint8_t)(r * 10 + c));
if (rt != r && rt >= 0 && rt <= 2) pop_set_redraw_fore((uint8_t)(rt * 10 + c));
}
static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
{
uint8_t order[MOB_MAX], n = 0, i, j;
@@ -1177,7 +1202,18 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
order[j] = i;
n++;
}
for (i = 0; i < n; i++) mob_render(&mobs[order[i]], pg);
for (i = 0; i < n; i++) {
/* Пометки соседа ставятся ЗДЕСЬ, а не в тике: у оригинала draw_mob
* (seg007:1147) считает ряд от той же координаты, по которой кусок
* рисуется. Стояли они в цикле тика — то есть по позиции ДО
* mob_tick_one, а рисовался кусок уже по новой. Пока он летит
* внутри ряда, старый и новый тайл совпадают и разницы не видно; но
* в кадр пересечения границы пометка указывала на прежний тайл, и
* передняя часть колонны кусок не перекрывала — ровно ОДИН кадр,
* как и наблюдалось (прогон 2026-08-13, комната 23, колонка 8). */
mob_mark_neighbour(&mobs[order[i]]);
mob_render(&mobs[order[i]], pg);
}
}
void pop_loose_mob_tick(void) __banked
+10
View File
@@ -103,3 +103,13 @@ uint8_t pop_seamless;
* КОМНАТЫ ОБЫЧНЫМ ХОДОМ и стартом уровня. */
uint8_t pop_nav_hold;
/* ВРЕМЕННО (замер фаз кадра 2026-08-13): четыре маркера-пустышки в
* РЕЗИДЕНТЕ. Брейкпоинт на каждом + printf totalcycles даёт длительность
* блоков: m1..m2 — логика (синий), m2..m3 — фон (зелёный), m3..m4 —
* спрайты (циан). В банк брейкпоинт ставить нельзя: 0xC000 общее окно. */
void pop_dbg_m1(void) { }
void pop_dbg_m2(void) { }
void pop_dbg_m3(void) { }
void pop_dbg_m4(void) { }
+5
View File
@@ -75,3 +75,8 @@ void pop_dbg_trap(void);
* Межбанковый: пишет pop_map, читает pop_map же, но через главный цикл. */
extern int8_t pop_jumped_mirror;
/* ВРЕМЕННО: маркеры фаз кадра (замер). */
void pop_dbg_m1(void); void pop_dbg_m2(void);
void pop_dbg_m3(void); void pop_dbg_m4(void);
+8
View File
@@ -372,6 +372,7 @@ int main(void)
pop_mirror_heal();
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
PROF(2); /* логика (тот же синий) */
pop_dbg_m1(); /* ЗАМЕР: начало логики */
kid_tick(); /* play_seq: следующий кадр */
pop_phys_tick(); /* K3: падение/приземление/стена */
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
@@ -447,6 +448,7 @@ int main(void)
}
PROF(4); /* слой фона */
pop_dbg_m2(); /* ЗАМЕР: конец логики / начало фона */
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
@@ -613,6 +615,7 @@ int main(void)
}
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
PROF(6); /* спрайты: страж, Kid, fore поверх */
pop_dbg_m3(); /* ЗАМЕР: конец фона / начало спрайтов */
/* check_mirror (seg003:0798) — ПЕРВАЯ строка draw_people
* (seg008:228A), до отрисовки кого бы то ни было: если Кид только что
* пролетел сквозь зеркало, тень должна родиться ДО того, как слот
@@ -645,6 +648,10 @@ int main(void)
if (pop_kid_hurt) pop_kid_hurt = 0;
}
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
/* ПЕРЕДНИЙ СЛОЙ по пометкам — последним в кадре, как foretable у
* оригинала (draw_tables, seg008:1373). Пометки ставят сами
* объекты вокруг себя (pop_redraw.h). */
pop_fore_needed();
pop_hp_draw(); /* полоса HP — только при изменении */
PROF(6); /* fore поверх Kid + борта */
/* Fore-слой Кида — В КОНЦЕ, после плиты и полосы HP: окно fore-клипа
@@ -673,6 +680,7 @@ int main(void)
pop_dbg_room_label(cur_room); /* номер комнаты палочками — в банке */
#endif
pop_dbg_m4(); /* ЗАМЕР: конец спрайтов */
gfx_wait_vsync();
if (flash_on) { /* строб: цвет держался один кадр растра */
flash_bg(0, 0, 0);