Разбор порядка отрисовки падающей плиты: найден корень, выбран вариант B

СИМПТОМ (пользователь, ур.11 к.14).  Кусок, отвалившийся от плиты нижнего
ряда, рисуется ПОВЕРХ соседней трясущейся плиты; при этом передний торец
соседа лежит поверх куска — порядок противоречив в разных частях
перекрытия.

КОРЕНЬ.  draw_mob (seg007:13E5) считает ряд объекта как
y_to_row_mod4(y) = (y+60)/63 % 4 - 1.  Из-за % 4 ряд 3 (кусок ушёл ниже
комнаты) и ряд −1 (кусок у потолка) дают ОДНО значение −1.  Оригинал
прогоняет его через get_tilepos -> get_tilepos_nominus и получает тайл 30,
а объекты с тайлом 30 рисуются в redraw_needed_tiles ПЕРВЫМИ, до всего
обхода тайлов.  Мы же передаём сырой −1 в мид-оверлей как «тайл объекта», и
гейт `row > pop_bg_obj_row` читает его как «объект в последнем ряду обхода»,
то есть «объект поверх всего», и отбрасывает возврат соседа.  Один и тот же
−1 у нас значит «сверху», у оригинала — «снизу».

Торец при этом виден потому, что приходит из ДРУГОГО слоя: draw_loose кладёт
loose_fram_bottom в backtable и foretable, минуя ptr_add_table, — тело плиты
обязан вернуть мид-оверлей, а его и выключает гейт.

ЧТО ПРОВЕРЕНО ЗАМЕРОМ (журнал решений в pop_dbg_ovl/pop_dbg_pass, зонды
ВРЕМЕННЫЕ и будут сняты):
 - на застывшем кадре: слот draw_y=194, r=−1, rt=2, оверлей позван, внутри
   отбрасывается гейтом;
 - пропуск оверлея на последнем кадре полёта ЗАКОНЕН: габарит куска уже
   ниже габарита тайла, перекрывать нечего;
 - в 13/23 кусок перекрывают 2-4 тайла (накопленно за полёт), включая СВОЮ
   клетку, — то есть «сосед справа» покрытие не исчерпывает;
 - перерисовок тайлов за кадр в 13/23: максимум 6, в покое 0.

ТУПИКИ, чтобы не ходить второй раз.  Клип объекта тут ни при чём:
add_mob_to_objtable ставит clip.right = 40, но клип применяется только при
chtab_flip_clip[chtab_id], а для chtab_6_environment там 0 — поле
игнорируется и в оригинале.  Пункт MOB-CLIP-RIGHT закрывается как
несуществующий.  Добавлять торец плиты в мид-оверлей тоже не надо: в
foretable он уже кладётся из fore_tile.

ВЫБРАН ВАРИАНТ B: классифицировать ряд объекта (вне 0..2 = корзина 30),
отдавать оверлею ориентир «раньше любого тайла» и расширить набор
перекрываемых тайлов на СВОЮ клетку.  Переносить проход отрисовки не нужно —
heal при этом не участвует, работа та же (оверлей = два блита в окне клипа),
разница с узким вариантом A всего один-два оверлея на кусок.

Заодно записана ОБЯЗАТЕЛЬНАЯ задача HEAL-WIDTH: ширины точечных heal'ов
взяты по клеткам (64), а фактический след плиты — 58/57 (замерено по
атласам: верх 32, правая грань 26 в подземелье и 25 во дворце).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 22:51:00 +03:00
parent cdac5746ac
commit 272cf8f195
6 changed files with 114 additions and 17 deletions
+46
View File
@@ -258,6 +258,52 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
## P0 — делаем сейчас
### <a id="heal-width"></a>HEAL-WIDTH. Ширина точечных heal'ов не совпадает со следом перерисовки — ОБЯЗАТЕЛЬНО
**Постановка (пользователь, 2026-08-18).** Ширины heal'ов взяты «на глаз по
клеткам», а не по реальному следу. Сжатие до фактического следа даёт около
**10 % площади** самой дорогой операции акселератора; по оценке пользователя
5-6 % реального выигрыша. Задача обязательная.
**Измерено** (каталоги атласов, спрайты плиты):
| часть плиты | подземелье | дворец |
|---|---|---|
| верх (`41/69/70`) | 32 × 13-14 | 32 × 13-14 |
| правая грань (`42/71/72`) | **26** × 15-16 | **25** × 15-16 |
То есть анимированный след ОДНОЙ плиты — `32 + 26 = 58` пикселей в
подземелье и `57` во дворце.
**Что стоит сейчас.** В коде уживаются ДВЕ разные договорённости:
- **60** = «свой тайл + свес соседа» — `pop_floor_bake` (там это записано
комментарием);
- **64** = «две клетки» — `pop_loose_shake_draw`, `pop_spike_redraw`
(«ширина 64 = обе ячейки»).
Для трясущейся плиты 64 избыточны: хватает 58 (подземелье) / 57 (дворец),
то есть **6 пикселей из 64 — впустую, 9,4 % площади heal'а**.
**И обратная сторона, которую тоже надо разобрать.** Ни 60, ни 64 не
покрывают того, что перерисовка РИСУЕТ: два полных `draw_tile` красят до
**90** пикселей (32 свои + 32 соседа + 26 правой грани соседа, уходящей в
третью клетку). Видимого артефакта отсюда пока нет — содержимое третьей
клетки статично и перерисовывается одинаково, а трясущийся сосед накрывает
эту область своим heal'ом, — но несоответствие «heal уже, чем след» надо
либо закрыть, либо записать как осознанное.
**Что сделать.** Свести ВСЕ точечные heal'ы (`pop_loose_shake_draw`,
`pop_spike_redraw`, `pop_chomp_redraw`, `pop_floor_bake`, `pop_loose_bake_empty`,
`pop_ceil_*`, коридор `mob_heal_*`) в таблицу «что рисует / что чистит» и
привести ширину каждого к фактическому следу. Ширины брать ИЗ АТЛАСА, а не
из номера клетки — они разные у двух тайлсетов (26 против 25).
**Проверка:** замер 13/23 до/после (heal'ы — зелёная фаза) плюс визуальный
прогон комнат с плитами, пиками и чомперами: сужение heal'а — самый прямой
способ получить «недочищенный хвост».
### <a id="l12-shadow"></a>L12-SHADOW. Уровень 12: встреча с тенью, бой, слияние
> **Статус 2026-08-13: код написан, хост-тесты зелёные (45 проверок,
+4 -14
View File
@@ -781,20 +781,10 @@ static void overlay_mid_tile(int row, int col)
} else if (t->bottom_id) {
pop_env_b(t->bottom_id, x, dby);
}
/* draw_loose(0) — ПЕРЕДНИЙ ТОРЕЦ плиты, последний вызов draw_tile2
* (seg008: draw_tile_right, draw_tile_anim_right, draw_tile_base,
* draw_tile_anim, draw_tile_bottom, draw_loose). У loose в
* pop_tile_table bottom_id = 0, поэтому ветка выше его НЕ рисует, и в
* оверлее торца не было вовсе: падающий кусок оставался ПОВЕРХ торца
* соседней плиты (найдено пользователем 2026-08-18, ур.11 к.14 —
* трясущаяся плита (2,6) и кусок, отвалившийся от (2,5)).
*
* Кадр берётся по ЖИВОМУ modif, как и верх плиты выше: оверлей рисуется
* поверх уже готовой картинки, и дрожащая плита обязана остаться
* дрожащей. */
if (code == 11 && row >= 0)
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(pop_loose_modif[row * 10 + col])],
x, dby);
/* draw_loose тут НЕТ, и это не забывчивость: в оригинале (seg008
* draw_loose) нижняя грань плиты идёт в add_backtable + add_foretable,
* мимо ptr_add_table — то есть в midtable не попадает никогда. Перекрыть
* ею падающий кусок должен ПЕРЕДНИЙ слой (fore_only_tile), а не оверлей. */
}
/* draw_tile_fore (seg008:690) — ТОЛЬКО передний слой (add_foretable): он
+6 -1
View File
@@ -107,7 +107,8 @@ void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages) __banked
* аргументов (30*2 делений за кадр даже когда рисовать нечего). */
void pop_redraw_needed(void) __banked
{
uint8_t i, row = 0, col = 0, left = 0;
uint8_t i, row = 0, col = 0, left = 0, cnt = 0; /* cnt — ВРЕМЕННО, замер */
pop_dbg_rdmax_tot = 0;
if (!rd_pending) return;
for (i = 0; i < NTILES; i++) {
if (rd_cnt[i]) {
@@ -121,6 +122,7 @@ void pop_redraw_needed(void) __banked
case POP_RD_CHOMP: pop_chomp_redraw(row, col); break;
default: break;
}
cnt++; /* ВРЕМЕННО: замер */
if (--rd_cnt[i]) left++;
else rd_kind[i] = 0;
}
@@ -130,9 +132,12 @@ void pop_redraw_needed(void) __banked
if (rda_cnt[i]) {
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
else pop_ceil_bake_empty(i);
cnt++; /* ВРЕМЕННО: замер */
if (--rda_cnt[i]) left++;
else rda_kind[i] = 0;
}
}
rd_pending = left;
pop_dbg_rdmax_tot = cnt; /* ВРЕМЕННО: замер */
if (cnt > pop_dbg_rdmax[0]) pop_dbg_rdmax[0] = cnt;
}
+40 -2
View File
@@ -1203,6 +1203,13 @@ static void mob_spawn(uint8_t room, int row, int col)
m->defer = 0;
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
m->draw_y = MOB_Y_NONE;
{ /* ВРЕМЕННО: журнал решения оверлея — копим за весь полёт */
uint8_t k = (uint8_t)(m - mobs);
pop_dbg_pass[k] = 0;
pop_dbg_ovl[k * 4 + 2] = 0;
pop_dbg_ovl[k * 4 + 3] = 0;
pop_dbg_tiles[k] = 0;
}
}
/* Отрыв куска в ТЕКУЩЕЙ комнате (обычный случай — pop_loose_tick). */
@@ -1581,7 +1588,24 @@ static void mob_overlay_neighbour(const mob_t *m)
int8_t rt = pop_y_to_row((int16_t)(m->draw_y - 18));
int8_t c = (int8_t)(m->col + 1);
int ytop, ybot;
uint8_t *dbg = &pop_dbg_ovl[(m - mobs) * 4]; /* ВРЕМЕННО: журнал решения */
{ /* ВРЕМЕННО: перебор ВСЕХ тайлов, чьи габариты пересекаются со спрайтом
* куска — чтобы понять, скольким тайлам реально нужно лечь поверх. */
int8_t rr, kk;
int t0 = m->draw_y - (int)mob_spr[2] + 1, t1 = m->draw_y;
for (rr = -1; rr <= 2; rr++)
for (kk = 0; kk <= 1; kk++) {
int8_t cc = (int8_t)(m->col + kk);
if (cc > 9) continue;
if (pop_tile_code(rr, cc) &&
t1 >= 63 * rr + 2 && t0 <= 63 * rr + 66)
pop_dbg_tiles[m - mobs] |= (uint8_t)(1 << ((rr + 1) * 2 + kk));
}
}
dbg[0] = (uint8_t)(r + 128); dbg[1] = (uint8_t)(rt + 128);
dbg[2] |= 1;
if (c > 9) return;
dbg[2] |= 2;
/* Габарит куска — по ЭКРАННОЙ координате, как и ряд выше: у куска,
* видимого из соседней комнаты, draw_y отличается от y на 192, и
* сравнение с габаритом тайла по y давало заведомо ложный ответ. */
@@ -1598,13 +1622,21 @@ static void mob_overlay_neighbour(const mob_t *m)
* Габарит тайла берём ПОЛНЫМ (63*row+2 .. +66) — какая именно часть
* соседа окажется поверх куска, решит уже окно клипа внутри. Сужать по
* реальной высоте кусков нельзя: у колонн и зеркала база высокая. */
if (r >= 0 && r <= 2 && pop_tile_code(r, c)) dbg[2] |= 4;
if (r >= 0 && r <= 2 && ybot >= 63 * r + 2 && ytop <= 63 * r + 66) dbg[2] |= 8;
if (r >= 0 && r <= 2 && pop_tile_code(r, c) &&
ybot >= 63 * r + 2 && ytop <= 63 * r + 66)
ybot >= 63 * r + 2 && ytop <= 63 * r + 66) {
dbg[2] |= 0x10; dbg[3]++;
pop_mob_overlay_tile(r, c, r, (int8_t)m->col, m->x, m->draw_y, mob_spr[2]);
}
if (rt != r && rt >= 0 && rt <= 2 && pop_tile_code(rt, c)) dbg[2] |= 0x20;
if (rt != r && rt >= 0 && rt <= 2 && ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66) dbg[2] |= 0x40;
if (rt != r && rt >= 0 && rt <= 2 && pop_tile_code(rt, c) &&
ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66)
ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66) {
dbg[2] |= 0x80; dbg[3]++;
pop_mob_overlay_tile(rt, c, r, (int8_t)m->col, m->x, m->draw_y, mob_spr[2]);
}
}
static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
{
@@ -1618,6 +1650,10 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
mob_t *m = &mobs[i];
/* Отбор по draw_y, а НЕ по комнате: кусок, только что провалившийся
* в комнату снизу, ещё виден из нашей (см. mob_tick_one). */
pop_dbg_pass[i] |= 1;
if (m->active) pop_dbg_pass[i] |= 2;
if (m->draw_y != MOB_Y_NONE) pop_dbg_pass[i] |= 4;
if ((uint8_t)(m->defer != 0) == want_defer) pop_dbg_pass[i] |= 8;
if (!m->active || m->draw_y == MOB_Y_NONE) continue;
if ((uint8_t)(m->defer != 0) != want_defer) continue;
for (j = n; j > 0 && mobs[order[j - 1]].draw_y < m->draw_y; j--)
@@ -1634,8 +1670,10 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
* в кадр пересечения границы пометка указывала на прежний тайл, и
* передняя часть колонны кусок не перекрывала — ровно ОДИН кадр,
* как и наблюдалось (прогон 2026-08-13, комната 23, колонка 8). */
pop_dbg_pass[order[i]] |= 0x10;
mob_mark_neighbour(&mobs[order[i]]);
mob_render(&mobs[order[i]], pg);
pop_dbg_pass[order[i]] |= 0x20;
mob_overlay_neighbour(&mobs[order[i]]);
}
}
+14
View File
@@ -87,6 +87,20 @@ void pop_dbg_trap(void) { pop_dbg_hits++; }
* смена комнаты видна по скачку tilepos, а нам удобнее явно). */
int8_t pop_dbg_obj[9];
/* ВРЕМЕННО (разбор оверлея падающего куска): по слоту mob.
* pop_dbg_pass[i] — что было в проходе отрисовки:
* bit0 рассмотрен, bit1 active, bit2 draw_y задан, bit3 defer совпал,
* bit4 нарисован, bit5 позван mob_overlay_neighbour.
* pop_dbg_ovl[i*4 + 0..3] — что решил mob_overlay_neighbour:
* [0] r+128, [1] rt+128, [2] флаги, [3] col.
* Флаги: bit0 вошли, bit1 c<=9, bit2 tile(r,c), bit3 габарит r,
* bit4 ВЫЗОВ по r, bit5 tile(rt,c), bit6 габарит rt, bit7 ВЫЗОВ по rt. */
uint8_t pop_dbg_pass[14];
uint8_t pop_dbg_ovl[14 * 4];
/* ВРЕМЕННО: какие тайлы РЕАЛЬНО перекрывают спрайт куска. Бит
* (ряд+1)*2 + (0 своя колонка / 1 соседняя), ряды −1..2. */
uint8_t pop_dbg_tiles[14];
/* Зеркало уровня 4 — см. pop_state.h. */
int8_t pop_jumped_mirror;
+4
View File
@@ -101,4 +101,8 @@ void pop_dbg_wh(uint16_t wh);
* 0 obj_tilepos, 1 frame, 2 action, 3 curr_col, 4 char_col_left,
* 5 char_col_right, 6 char_top_row, 7 char_bottom_row, 8 комната. */
extern int8_t pop_dbg_obj[9];
/* ВРЕМЕННО: разбор оверлея падающего куска (см. pop_state.c). */
extern uint8_t pop_dbg_pass[14];
extern uint8_t pop_dbg_ovl[14 * 4];
extern uint8_t pop_dbg_tiles[14];
void pop_dbg_kidobj(void) __banked;