Плиты нижнего ряда пропадали без кадров падения: draw_mob рисует и соседей
Уровень 11 комната 14: плита ряда 2 уходит в комнату снизу на первом же move_loose (спавн y=191 при границе ряда 188), а у нас кусок в чужой комнате не рисовался вовсе — плита исчезала мгновенно. Оригинал (seg007:13E5) рисует его ещё три кадра, выглядывающим из нижней кромки (+192), и симметрично из комнаты сверху (−189). У куска появилась экранная координата draw_y; по ней идут отрисовка, heal, порядок относительно Кида, пометки соседа и оверлей, отбор в проходе — по ней же, а не по комнате. Ссылки вверх/вниз кэшируются. Цена (13/23): зелёная 419 562 -> 427 242, работа 880 272 -> 888 984. Первый вариант стоил втрое дороже (445 248) из-за безусловной пометки по прошлой нарисованной позиции — у куска из соседней комнаты она в 192 пикселях, и объединение растягивалось на весь экран; гейт вернул 18 000. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3238,3 +3238,69 @@ MAME на резидентном `pop_dbg_trap`; сборщик — `collect_kid
|
||||
`pop_cd[].fpw`. Подозрение на паддинг кадров в атласе
|
||||
(`png_strip_padding_tradeoff`) либо на мой пересчёт переднего края мимо
|
||||
боевого `char_x_forward_edge`. Проверять отдельно.
|
||||
|
||||
---
|
||||
|
||||
<a id="mob-neighbour-room"></a>
|
||||
## MOB-NEIGHBOUR-ROOM. Плиты нижнего ряда пропадали без кадров падения
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-18).** Уровень 11, комната 14: Кид бежит
|
||||
по ряду проваливающихся плит, они трясутся и «падают сразу полностью
|
||||
пропадая, а должен быть промежуточный кадр». Догадка пользователя — «для
|
||||
плит на 2-ом ряду мы рисуем только тряску» — оказалась верной по симптому и
|
||||
почти верной по причине.
|
||||
|
||||
**Корень.** Порт `draw_mob` (seg007:13E5) был неполным. У оригинала кусок
|
||||
рисуется НЕ ТОЛЬКО в своей комнате:
|
||||
|
||||
```c
|
||||
if (curmob.room == drawn_room) { if (curmob.y >= 210) return; }
|
||||
else if (curmob.room == room_B) { if (ABS((sbyte)ypos) >= 18) return; curmob.y += 192; }
|
||||
else if (curmob.room == room_A) { if (curmob.y < 174) return; ypos = curmob.y - 189; }
|
||||
else return;
|
||||
```
|
||||
|
||||
У нас на этом месте стояло `if (!mt_here) return;` — «чужая комната: считаем,
|
||||
но не рисуем».
|
||||
|
||||
Почему страдал ИМЕННО ряд 2: кусок спавнится на `y_loose_land[row+1]`, для
|
||||
ряда 2 это **191**, а граница ряда `y_something[3]` — **188**. То есть на
|
||||
ПЕРВОМ же `move_loose` он уходит в комнату снизу и у нас переставал
|
||||
рисоваться мгновенно. Ряды 0 и 1 летят внутри комнаты, поэтому там всё было
|
||||
видно. Промежуточных кадров ровно три — пока `|y| < 18`.
|
||||
|
||||
**Фикс.** У куска появилась отдельная ЭКРАННАЯ координата `draw_y` (своя
|
||||
комната / +192 из комнаты снизу / −189 из комнаты сверху, `MOB_Y_NONE` =
|
||||
не рисуется). По ней идут отрисовка, `prev_y`/heal, порядок относительно
|
||||
Кида, пометки соседа и оверлей; отбор в проходе отрисовки — по `draw_y`, а не
|
||||
по комнате. Ссылки вверх/вниз кэшируются (иначе `pop_room_link` мапил бы W0
|
||||
каждый кадр).
|
||||
|
||||
Вторым заходом пришлось поправить `mob_overlay_neighbour`: ряд там уже считался
|
||||
по `draw_y`, а габарит спрайта и вызов `pop_mob_overlay_tile` остались по `y` —
|
||||
для куска из соседней комнаты это расходится на 192, и пересечение с габаритом
|
||||
тайла считалось заведомо неверно (порядок переднего слоя).
|
||||
|
||||
**Цена — и урок про то, ЧТО именно дорого.** Замер 13/23:
|
||||
|
||||
| секция | до | первый вариант | после гейта |
|
||||
|---|---:|---:|---:|
|
||||
| работа | 880 272 | 898 578 | **888 984** |
|
||||
| зелёная | 419 562 | 445 248 | **427 242** |
|
||||
| циан | 380 202 | 379 818 | 378 864 |
|
||||
|
||||
Первый вариант метил «фон трогали» по ПРОШЛОЙ нарисованной позиции
|
||||
безусловно. У куска, видимого из соседней комнаты, она отличается от `y` на
|
||||
192 — объединение прямоугольников растягивалось почти на весь экран и тянуло
|
||||
за собой лишние перерисовки. Гейт `m->room != pop_t_room` вернул 18 000 из
|
||||
26 000.
|
||||
|
||||
Я сперва списал рост на клипованный блит (кусок у кромки по определению
|
||||
клипуется, а клипованный путь дороже: 14 088 против 10 422) — замер это
|
||||
опроверг: дело было в пометке. Остаток +7 680 в зелёной (+1,8 %) —
|
||||
собственно отрисовка трёх кадров на плиту; зелёная остаётся ниже растрового
|
||||
кадра 430 000.
|
||||
|
||||
Host-тесты 5106 проверок без расхождений (`pop_room.c` они не линкуют —
|
||||
проверка визуальная, в MAME: уровень 11 комната 14, отделившаяся плита видна
|
||||
три кадра).
|
||||
|
||||
@@ -1116,6 +1116,11 @@ typedef struct {
|
||||
uint8_t defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
|
||||
int x; /* левый край (POP_COL_XH*8) */
|
||||
int y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
|
||||
/* Где кусок рисуется НА ЭКРАНЕ в этом кадре, MOB_Y_NONE = не рисуется.
|
||||
* Отдельно от y, потому что кусок виден и из СОСЕДНЕЙ комнаты: порт
|
||||
* draw_mob (seg007:13E5) сдвигает его на +192 (комната снизу) или на
|
||||
* −189 (комната сверху). См. mob_tick_one. */
|
||||
int draw_y;
|
||||
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
|
||||
int8_t speed;
|
||||
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
|
||||
@@ -1197,6 +1202,7 @@ static void mob_spawn(uint8_t room, int row, int col)
|
||||
m->clean = 0;
|
||||
m->defer = 0;
|
||||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||||
m->draw_y = MOB_Y_NONE;
|
||||
}
|
||||
|
||||
/* Отрыв куска в ТЕКУЩЕЙ комнате (обычный случай — pop_loose_tick). */
|
||||
@@ -1235,6 +1241,7 @@ static void mob_spawn_copy(const mob_t *src)
|
||||
m->clean = 0;
|
||||
m->defer = 0;
|
||||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||||
m->draw_y = MOB_Y_NONE;
|
||||
/* Кусок рождается ВНУТРИ mob_tick_one (loose_fall сбил плиту), и слот ему
|
||||
* mob_alloc выдаёт с начала таблицы — то есть, скорее всего, УЖЕ
|
||||
* пройденный циклом pop_loose_mob_tick. Своей пометки «фон трогали» этот
|
||||
@@ -1273,15 +1280,30 @@ void pop_loose_mob_reset(void) __banked
|
||||
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
|
||||
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
|
||||
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
|
||||
/* Соседи ОТРИСОВАННОЙ комнаты сверху/снизу — для draw_mob (см. mob_tick_one).
|
||||
* Кэш, а не вызов каждый кадр: pop_room_link лезет в страницу уровня, то есть
|
||||
* мапит и размапливает W0. Инвалидируется сменой комнаты. */
|
||||
static uint8_t mob_link_room, mob_link_up, mob_link_dn;
|
||||
|
||||
static void mob_links(void)
|
||||
{
|
||||
if (mob_link_room == pop_t_room) return;
|
||||
mob_link_room = pop_t_room;
|
||||
mob_link_up = pop_room_link(pop_t_room, 2);
|
||||
mob_link_dn = pop_room_link(pop_t_room, 3);
|
||||
}
|
||||
|
||||
void pop_loose_mob_room_changed(void) __banked
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < MOB_MAX; i++) {
|
||||
mobs[i].defer = 0;
|
||||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||||
mobs[i].draw_y = MOB_Y_NONE;
|
||||
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
|
||||
if (!mobs[i].active) mobs[i].clean = 0;
|
||||
}
|
||||
mob_link_room = 0; /* кэш соседей — под новую комнату */
|
||||
}
|
||||
|
||||
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
|
||||
@@ -1328,6 +1350,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* pop_loose_mob_tick, и слоты он обходит последовательно. */
|
||||
mt_m = m; mt_pg = pg;
|
||||
if (!mt_m->active && !mt_m->clean) return;
|
||||
mt_m->draw_y = MOB_Y_NONE; /* по умолчанию не рисуемся; решим в конце */
|
||||
mt_here = (uint8_t)(mt_m->room == pop_t_room);
|
||||
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
||||
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
||||
@@ -1430,7 +1453,33 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
mt_m->active = 0; mt_m->clean = 2;
|
||||
return;
|
||||
}
|
||||
if (!mt_here) return; /* чужая комната — считаем, но не рисуем */
|
||||
/* Порт ГОЛОВЫ draw_mob (seg007:13E5). Кусок виден НЕ ТОЛЬКО в своей
|
||||
* комнате: оригинал рисует его ещё и когда он только что провалился в
|
||||
* комнату СНИЗУ (пока |y| < 18 — со сдвигом +192, то есть выглядывает из
|
||||
* нижней кромки) или летит в комнате СВЕРХУ и уже подошёл к её низу
|
||||
* (y >= 174 — со сдвигом −189, вываливается из верхней кромки).
|
||||
*
|
||||
* Без этого плита, падающая из НИЖНЕГО ряда, исчезала мгновенно: она
|
||||
* уходит в комнату снизу на ПЕРВОМ же move_loose (спавн y = 191 при
|
||||
* границе ряда 188), и у нас после этого не рисовалась вовсе. Ряды 0 и 1
|
||||
* летят внутри комнаты, поэтому баг был виден только на ряду 2 — ровно
|
||||
* как заметил пользователь (уровень 11, комната 14). */
|
||||
if (mt_here) {
|
||||
if (mt_m->y >= 210) return; /* ниже поля — оригинал не рисует */
|
||||
mt_m->draw_y = mt_m->y;
|
||||
} else {
|
||||
mob_links();
|
||||
if (mt_m->room == mob_link_dn) {
|
||||
int8_t yb = (int8_t)mt_m->y; /* ABS((sbyte)ypos) < 18 */
|
||||
if (yb >= 18 || yb <= -18) return;
|
||||
mt_m->draw_y = mt_m->y + 192;
|
||||
} else if (mt_m->room == mob_link_up) {
|
||||
if (mt_m->y < 174) return;
|
||||
mt_m->draw_y = mt_m->y - 189;
|
||||
} else {
|
||||
return; /* дальше соседей не рисуем */
|
||||
}
|
||||
}
|
||||
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
|
||||
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
|
||||
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
|
||||
@@ -1439,11 +1488,11 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
|
||||
* совпадении тайлов — если он ниже по y. */
|
||||
{
|
||||
int8_t mrow = pop_y_to_row((int16_t)mt_m->y); /* y_to_row_mod4 */
|
||||
int8_t mrow = pop_y_to_row((int16_t)mt_m->draw_y); /* y_to_row_mod4 */
|
||||
uint8_t over;
|
||||
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
|
||||
else if (mt_m->col != pop_bg_obj_col) over = (mt_m->col > pop_bg_obj_col);
|
||||
else over = (mt_m->y > pop_cd[POP_CH_KID].fpy);
|
||||
else over = (mt_m->draw_y > pop_cd[POP_CH_KID].fpy);
|
||||
/* РИСОВАНИЯ ЗДЕСЬ БОЛЬШЕ НЕТ — только решение, в какой проход кусок
|
||||
* пойдёт. Пока heal и вывод шли в одной итерации, heal СЛЕДУЮЩЕГО
|
||||
* куска срезал верхнюю кромку уже нарисованного предыдущего: на 13-м
|
||||
@@ -1488,8 +1537,8 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
* отрисовке тайла на кусок за кадр (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 r = pop_y_to_row((int16_t)m->draw_y);
|
||||
int8_t rt = pop_y_to_row((int16_t)(m->draw_y - 18));
|
||||
int8_t c = (int8_t)(m->col + 1);
|
||||
/* ПОЛОСА У ПОТОЛКА (ряд −1). Её передние куски (нижняя кромка потолка и
|
||||
* кадр плиты-потолка) живут в трёх верхних строках поля — dby = 2 при
|
||||
@@ -1504,7 +1553,7 @@ static void mob_mark_neighbour(const mob_t *m)
|
||||
* плита (−1,5) перекрывала потолок (−1,6)). В оригинале порядок даёт
|
||||
* сама структура таблиц: кусок идёт в midtable (draw_mobs), а полоса
|
||||
* потолка — в foretable, и та рисуется позже (draw_tables, seg008:1373). */
|
||||
if (m->y <= 18) {
|
||||
if (m->draw_y <= 18) {
|
||||
/* Пустой тайл полосы потолка не рисует В ПЕРЕДНЕМ СЛОЕ ничего
|
||||
* (bottom_id = 0, FORE_ANY = 0), а пометка стоит потом двух трамплинов
|
||||
* — в банк 7 за разбором пометок и в банк 2 за самой отрисовкой,
|
||||
@@ -1528,13 +1577,16 @@ static void mob_mark_neighbour(const mob_t *m)
|
||||
* последовательностью вызовов. */
|
||||
static void mob_overlay_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 r = pop_y_to_row((int16_t)m->draw_y);
|
||||
int8_t rt = pop_y_to_row((int16_t)(m->draw_y - 18));
|
||||
int8_t c = (int8_t)(m->col + 1);
|
||||
int ytop, ybot;
|
||||
if (c > 9) return;
|
||||
ytop = m->y - (int)mob_spr[2] + 1; /* верх спрайта куска */
|
||||
ybot = m->y;
|
||||
/* Габарит куска — по ЭКРАННОЙ координате, как и ряд выше: у куска,
|
||||
* видимого из соседней комнаты, draw_y отличается от y на 192, и
|
||||
* сравнение с габаритом тайла по y давало заведомо ложный ответ. */
|
||||
ytop = m->draw_y - (int)mob_spr[2] + 1; /* верх спрайта куска */
|
||||
ybot = m->draw_y;
|
||||
/* ПРЕДУСЛОВИЯ ПРОВЕРЯЕМ ЗДЕСЬ, до вызова в банк 2. pop_mob_overlay_tile
|
||||
* живёт в банке 2, то есть каждый вызов из банка 7 платит межбанковый
|
||||
* трамплин (~8 900 такта); а условий всего два — тайл не пуст и габарит
|
||||
@@ -1548,10 +1600,10 @@ static void mob_overlay_neighbour(const mob_t *m)
|
||||
* реальной высоте кусков нельзя: у колонн и зеркала база высокая. */
|
||||
if (r >= 0 && r <= 2 && pop_tile_code(r, c) &&
|
||||
ybot >= 63 * r + 2 && ytop <= 63 * r + 66)
|
||||
pop_mob_overlay_tile(r, c, r, (int8_t)m->col, m->x, m->y, mob_spr[2]);
|
||||
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) &&
|
||||
ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66)
|
||||
pop_mob_overlay_tile(rt, c, r, (int8_t)m->col, m->x, m->y, mob_spr[2]);
|
||||
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)
|
||||
@@ -1564,9 +1616,11 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
|
||||
if (!mobs_live) return;
|
||||
for (i = 0; i < MOB_MAX; i++) {
|
||||
mob_t *m = &mobs[i];
|
||||
if (!m->active || m->room != pop_t_room) continue;
|
||||
/* Отбор по draw_y, а НЕ по комнате: кусок, только что провалившийся
|
||||
* в комнату снизу, ещё виден из нашей (см. mob_tick_one). */
|
||||
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]].y < m->y; j--)
|
||||
for (j = n; j > 0 && mobs[order[j - 1]].draw_y < m->draw_y; j--)
|
||||
order[j] = order[j - 1];
|
||||
order[j] = i;
|
||||
n++;
|
||||
@@ -1622,6 +1676,12 @@ void pop_loose_mob_tick(void) __banked
|
||||
* Запас сверху — на путь, пройденный за кадр (speed до 29): heal
|
||||
* стирает ПРОШЛУЮ позицию, она выше текущей. */
|
||||
pop_cd_touch(MOB_X0(m->x), m->y - 27 + POP_YOFF, MOB_W, 64);
|
||||
/* ...и по ПРОШЛОЙ нарисованной позиции: у куска, видимого из соседней
|
||||
* комнаты, экранная координата отличается от y на 192 — коридор heal'а
|
||||
* лежит совсем не там, где коридор по y. В пакете это четыре
|
||||
* сравнения, настоящая пометка всё равно одна. */
|
||||
if (m->room != pop_t_room && m->prev_y[pg] != MOB_Y_NONE)
|
||||
pop_cd_touch(MOB_X0(m->x), m->prev_y[pg] - 27 + POP_YOFF, MOB_W, 64);
|
||||
mob_tick_one(m, pg);
|
||||
if (m->active) live++;
|
||||
}
|
||||
@@ -1682,20 +1742,20 @@ static void mob_render(mob_t *m, uint8_t pg)
|
||||
* Окно ставим только когда кусок реально задевает борт: внутри поля блиты
|
||||
* идут быстрым путём (10 422 против 14 088). */
|
||||
{
|
||||
int ytop = m->y - 16 + POP_YOFF, ybot = m->y + POP_YOFF;
|
||||
int ytop = m->draw_y - 16 + POP_YOFF, ybot = m->draw_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) {
|
||||
pop_mem_b(mob_spr, m->x, m->y);
|
||||
pop_mem_b(mob_spr, m->x, m->draw_y);
|
||||
} else {
|
||||
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);
|
||||
pop_env_b(74, m->x, m->draw_y);
|
||||
pop_env_b(70, m->x, m->draw_y - 3);
|
||||
pop_env_b(72, m->x + 32, m->draw_y - 1);
|
||||
}
|
||||
if (clipped) pop_t_win_clear();
|
||||
}
|
||||
pop_cd_unmute();
|
||||
m->prev_y[pg] = m->y;
|
||||
m->prev_y[pg] = m->draw_y;
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user