Compare commits

..

2 Commits

Author SHA1 Message Date
snark13 09f32ce834 Уровни 10-12 прошли предварительный тест; HEAL-WIDTH связан с G8
Уровень 11 комната 14 проверена пользователем визуально после варианта B —
порядок падающего куска, соседней плиты и Кида корректен.  На 10 и 12
багов не найдено.

HEAL-WIDTH и G8 сведены как две половины одной темы: G8 про ширину ЗАПЕЧКИ
соседнего тайла (60 вместо 28 нужных, плюс draw_tile соседа дважды на
пометку), HEAL-WIDTH про ширину HEAL'ов (64 вместо фактических 58/57).
Оговорка из G8 перенесена: 60 = 32 свой тайл + 28 собственный свес, для
запечки самого тайла это минимум, сужать можно только пометку СОСЕДА.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 00:00:23 +03:00
snark13 4db60c750f Вариант B: кусок с завёрнутым рядом рисуется под всем (корзина 30)
Порт правила оригинала, разобранного в 272cf8f.  y_to_row_mod4 даёт −1 и
для куска выше потолка, и для ушедшего ниже комнаты; get_tilepos_nominus
сводит оба в тайл 30, а объекты тайла 30 рисуются в redraw_needed_tiles
ПЕРВЫМИ, до всего обхода тайлов.

Что сделано:
 - defer = 0 для таких кусков: они под всем, включая Кида.  Раньше
   сравнение рядов читало −1 как «обходится последним» = «поверх всего»;
 - оверлею отдаётся ориентир 3 («раньше любого ряда 2,1,0») вместо сырого
   −1 — гейт other_overlay_tile перестал отбрасывать возврат соседа, из-за
   чего тело плиты не возвращалось и оставался только её торец из
   переднего слоя;
 - в набор перекрываемых тайлов добавлена СВОЯ клетка (только для этого
   случая: у куска в обычном ряду объект вливается в midtable после частей
   своего тайла, и перерисовывать её нельзя).

Отладочная обвязка разбора (журнал решений оверлея, маска перекрывающих
тайлов) снята; счётчик перерисовок за кадр в pop_redraw_needed оставлен —
он дешёвый и пригодится для HEAL-WIDTH.

ЗАМЕР 13/23, 3032 кадра, против тега mob-order-B-start:
  работа  888 984 -> 913 848  (+24 864)
  синяя   159 804 -> 159 810
  зелёная 427 242 -> 440 418  (+13 176)
  циан    378 864 -> 393 000  (+14 136)
Период кадра не изменился: 4 растра в 23 кадрах, 5 в двух.

Зелёная вышла за растровый кадр (440 418 против 430 000).  Детализация:
pop_loose_tick 185 826, из них pop_loose_mob_tick 168 180; тробы +
redraw_needed 337 800.  Разбор и план возврата тактов — HEAL-WIDTH.

Визуальная проверка комнаты 14 за пользователем: поймать кадр с куском
снимками мне не удалось.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 23:10:41 +03:00
4 changed files with 99 additions and 54 deletions
+43
View File
@@ -3337,3 +3337,46 @@ draw_tile_anim(); draw_tile_bottom(0); draw_loose(0);
`draw_tile_anim_right` — то есть анимированной правой грани СОСЕДА (пики, `draw_tile_anim_right` — то есть анимированной правой грани СОСЕДА (пики,
ворота, loose). Симптома на это пока не видели; если всплывёт «правая грань ворота, loose). Симптома на это пока не видели; если всплывёт «правая грань
соседа под персонажем/куском» — смотреть сюда. соседа под персонажем/куском» — смотреть сюда.
### Вариант B: порт корзины 30 (2026-08-18)
Реализовано то, что разобрано в коммите `272cf8f`. Кусок, у которого ряд
объекта вне 0..2 (`y_to_row_mod4` даёт −1 и «выше потолка», и «ниже пола»),
считается объектом ТАЙЛА 30 — а такие оригинал рисует ПЕРВЫМИ, до всего
обхода тайлов. Два следствия:
- `defer = 0` — кусок идёт под всем, включая Кида (раньше сравнение рядов
давало обратное: «−1 обходится последним» читалось как «поверх»);
- оверлею отдаётся ориентир **3** («раньше любого ряда обхода 2,1,0»)
вместо сырого −1, и в набор перекрываемых тайлов добавляется **своя
клетка** — у куска в обычном ряду её перерисовывать нельзя, там объект
вливается в midtable уже после частей своего тайла.
**Замер 13/23 (3032 кадра, зонды фаз + детализация зелёной):**
| секция | тег `mob-order-B-start` | после B | дельта |
|---|---:|---:|---:|
| работа | 888 984 | **913 848** | +24 864 |
| синяя | 159 804 | 159 810 | +6 |
| зелёная | 427 242 | **440 418** | +13 176 |
| циан | 378 864 | 393 000 | +14 136 |
Период кадра не изменился: 4 растра в 23 кадрах, 5 в двух — как и до правки.
**Зелёная вышла за растровый кадр** (440 418 против 430 000) при цели
400 000. Детализация показывает, где именно:
| участок зелёной | максимум |
|---|---:|
| `pop_loose_tick` целиком | 185 826 |
| — из них `pop_loose_mob_tick` | **168 180** |
| — циклы по тайлам | 23 736 |
| — `check_loose_fall_on_kid` | 5 868 |
| тробы + `pop_redraw_needed` | 337 800 |
| шов и прочее | 11 964 |
(максимумы взяты по разным кадрам, поэтому не складываются в общий).
Основной кандидат на возврат этих тактов — задача
[HEAL-WIDTH](TASKS_OPEN.md#heal-width): heal'ы кусков и плит берут ширину по
клеткам, а фактический след — 58/57.
+1 -1
View File
@@ -55,7 +55,7 @@ ESC выход.
## Статус (2026-08-18) ## Статус (2026-08-18)
**Уровни 1-3 приняты, 4-9 прошли предварительный тест.** Работает: комнаты и **Уровни 1-3 приняты, 4-12 прошли предварительный тест.** Работает: комнаты и
переходы, Kid (анимация, управление, коллизия/падение, зацеп/подтягивание/ переходы, Kid (анимация, управление, коллизия/падение, зацеп/подтягивание/
спуск, окклюзия), кнопки и ворота, пики, чомперы, проваливающиеся полы, дверь спуск, окклюзия), кнопки и ворота, пики, чомперы, проваливающиеся полы, дверь
уровня и переход на следующий, меч и бой, стражи с ИИ (включая скелета и уровня и переход на следующий, меч и бой, стражи с ИИ (включая скелета и
+11 -1
View File
@@ -44,7 +44,10 @@
| 7 | **предварительно пройден** (2026-08-18) | [SPIKE-BAKED](BUGS_CLOSED.md#spike-baked) — выдвинутые пики консервировались в фон запечкой соседней кнопки и оставались навсегда (`980d48c`); [DIED-ON-BUTTON](BUGS_CLOSED.md#died-on-button) — смерть на кнопке не ломала её насовсем: до `check_press` труп не доходил, самой `died_on_button` не было, а таймер связи переживал респавн (`6feab5d`) | | 7 | **предварительно пройден** (2026-08-18) | [SPIKE-BAKED](BUGS_CLOSED.md#spike-baked) — выдвинутые пики консервировались в фон запечкой соседней кнопки и оставались навсегда (`980d48c`); [DIED-ON-BUTTON](BUGS_CLOSED.md#died-on-button) — смерть на кнопке не ломала её насовсем: до `check_press` труп не доходил, самой `died_on_button` не было, а таймер связи переживал респавн (`6feab5d`) |
| 8 | **предварительно пройден** (2026-08-18) | [GUARD-RESPAWN-COL0](BUGS_CLOSED.md#guard-respawn-col0) — при возврате в комнату страж телепортировался в колонку 0 и падал насмерть: колонку несёт `guards_x`, а не тайл (`c40ae3f`); [SEAM-FIGHT-FLICKER](BUGS_CLOSED.md#seam-fight-flicker) — бой у шва перерисовывал комнату туда-обратно: `leave_room` запрещает уход ещё и на кадрах меча (`a498255`, сверено с SDLPoP покадрово) | | 8 | **предварительно пройден** (2026-08-18) | [GUARD-RESPAWN-COL0](BUGS_CLOSED.md#guard-respawn-col0) — при возврате в комнату страж телепортировался в колонку 0 и падал насмерть: колонку несёт `guards_x`, а не тайл (`c40ae3f`); [SEAM-FIGHT-FLICKER](BUGS_CLOSED.md#seam-fight-flicker) — бой у шва перерисовывал комнату туда-обратно: `leave_room` запрещает уход ещё и на кадрах меча (`a498255`, сверено с SDLPoP покадрово) |
| 9 | **предварительно пройден** (2026-08-18) | багов не найдено | | 9 | **предварительно пройден** (2026-08-18) | багов не найдено |
| 10+ | не начат | | | 10 | **предварительно пройден** (2026-08-18) | багов не найдено |
| 11 | **предварительно пройден** (2026-08-18) | [MOB-NEIGHBOUR-ROOM](BUGS_CLOSED.md#mob-neighbour-room) — плиты нижнего ряда пропадали без кадров падения (кусок в соседней комнате не рисовался, `ed5615a`); порядок куска относительно соседней плиты и Кида — порт корзины 30 (`4db60c7`) |
| 12 | **предварительно пройден** (2026-08-18) | багов не найдено |
| 13+ | не начат | |
«Предварительно пройден» ≠ «принят»: уровни 4-6 пройдены прогоном по «Предварительно пройден» ≠ «принят»: уровни 4-6 пройдены прогоном по
сценарию, а не обходом ВСЕХ комнат — полные обходы делаются по готовности сценарию, а не обходом ВСЕХ комнат — полные обходы делаются по готовности
@@ -303,6 +306,13 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
прогон комнат с плитами, пиками и чомперами: сужение heal'а — самый прямой прогон комнат с плитами, пиками и чомперами: сужение heal'а — самый прямой
способ получить «недочищенный хвост». способ получить «недочищенный хвост».
**Родня — [G8](../docs/perf_green_phase.md) (пометка соседа узкой полосой).**
Это две половины одной темы, и брать их логично вместе: G8 про ширину
ЗАПЕЧКИ соседнего тайла (60 вместо 28 нужных, да ещё `draw_tile` соседа
дважды на пометку), HEAL-WIDTH — про ширину HEAL'ов (64 вместо 58/57).
Числа там уже разобраны: 60 = 32 свой тайл + 28 собственный свес, и для
запечки САМОГО тайла 60 минимальны — сужать можно только пометку СОСЕДА.
### <a id="l12-shadow"></a>L12-SHADOW. Уровень 12: встреча с тенью, бой, слияние ### <a id="l12-shadow"></a>L12-SHADOW. Уровень 12: встреча с тенью, бой, слияние
+44 -52
View File
@@ -1203,13 +1203,6 @@ static void mob_spawn(uint8_t room, int row, int col)
m->defer = 0; m->defer = 0;
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE; m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
m->draw_y = 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). */ /* Отрыв куска в ТЕКУЩЕЙ комнате (обычный случай — pop_loose_tick). */
@@ -1497,7 +1490,18 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
{ {
int8_t mrow = pop_y_to_row((int16_t)mt_m->draw_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; uint8_t over;
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */ if (mrow < 0 || mrow > 2) {
/* КОРЗИНА 30 (get_tilepos_nominus, seg006:110). Ряд объекта вне
* комнаты: y_to_row_mod4 берёт остаток по 4, поэтому «выше
* потолка» и «ниже пола» дают одно и то же −1, а оригинал сводит
* оба в тайл 30. Объекты этого тайла рисуются ПЕРВЫМИ —
* draw_objtable_items_at_tile(30) стоит в redraw_needed_tiles до
* всего обхода тайлов. Значит кусок под ВСЕМ, включая Кида; до
* сих пор сравнение рядов давало обратное («−1 обходится
* последним» = поверх). */
over = 0;
}
else 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 if (mt_m->col != pop_bg_obj_col) over = (mt_m->col > pop_bg_obj_col);
else over = (mt_m->draw_y > pop_cd[POP_CH_KID].fpy); else over = (mt_m->draw_y > pop_cd[POP_CH_KID].fpy);
/* РИСОВАНИЯ ЗДЕСЬ БОЛЬШЕ НЕТ — только решение, в какой проход кусок /* РИСОВАНИЯ ЗДЕСЬ БОЛЬШЕ НЕТ — только решение, в какой проход кусок
@@ -1586,30 +1590,30 @@ static void mob_overlay_neighbour(const mob_t *m)
{ {
int8_t r = pop_y_to_row((int16_t)m->draw_y); 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 rt = pop_y_to_row((int16_t)(m->draw_y - 18));
int8_t c = (int8_t)(m->col + 1); /* КОРЗИНА 30 оригинала (get_tilepos_nominus, seg006:110): ряд объекта вне
* комнаты. y_to_row_mod4 берёт остаток по 4, поэтому «выше потолка» и
* «ниже пола» дают одно и то же −1 — оригинал сводит оба случая в тайл 30
* и рисует такие объекты ПЕРВЫМИ, до всего обхода тайлов
* (draw_objtable_items_at_tile(30) в redraw_needed_tiles).
*
* Отсюда два отличия от обычного куска:
* - ориентир «тайл объекта» = 3, то есть РАНЬШЕ любого ряда обхода
* (2,1,0). Сюда уходил сырой −1, и гейт other_overlay_tile
* `row > pop_bg_obj_row` читал его наоборот — «объект поверх всего» —
* и соседа не возвращал вовсе (тело плиты пропадало, оставался
* только её торец из переднего слоя);
* - перекрыть кусок должна и СВОЯ клетка, а не только сосед справа:
* после объекта у оригинала рисуются ВСЕ тайлы. У куска в обычном
* ряду своя клетка не перерисовывается — там объект вливается в
* midtable уже ПОСЛЕ частей своего тайла. */
uint8_t b30 = (uint8_t)(r < 0 || r > 2);
int8_t oref = b30 ? (int8_t)3 : r;
int8_t c, cend = (int8_t)(m->col + 1);
int ytop, ybot; int ytop, ybot;
uint8_t *dbg = &pop_dbg_ovl[(m - mobs) * 4]; /* ВРЕМЕННО: журнал решения */ if (cend > 9) cend = 9;
{ /* ВРЕМЕННО: перебор ВСЕХ тайлов, чьи габариты пересекаются со спрайтом /* Габарит куска — по ЭКРАННОЙ координате: у куска, видимого из соседней
* куска — чтобы понять, скольким тайлам реально нужно лечь поверх. */ * комнаты, draw_y отличается от y на 192. */
int8_t rr, kk; ytop = m->draw_y - (int)mob_spr[2] + 1;
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 давало заведомо ложный ответ. */
ytop = m->draw_y - (int)mob_spr[2] + 1; /* верх спрайта куска */
ybot = m->draw_y; ybot = m->draw_y;
/* ПРЕДУСЛОВИЯ ПРОВЕРЯЕМ ЗДЕСЬ, до вызова в банк 2. pop_mob_overlay_tile /* ПРЕДУСЛОВИЯ ПРОВЕРЯЕМ ЗДЕСЬ, до вызова в банк 2. pop_mob_overlay_tile
* живёт в банке 2, то есть каждый вызов из банка 7 платит межбанковый * живёт в банке 2, то есть каждый вызов из банка 7 платит межбанковый
@@ -1620,21 +1624,15 @@ static void mob_overlay_neighbour(const mob_t *m)
* ничего не рисовало. * ничего не рисовало.
* *
* Габарит тайла берём ПОЛНЫМ (63*row+2 .. +66) — какая именно часть * Габарит тайла берём ПОЛНЫМ (63*row+2 .. +66) — какая именно часть
* соседа окажется поверх куска, решит уже окно клипа внутри. Сужать по * соседа окажется поверх куска, решит уже окно клипа внутри. */
* реальной высоте кусков нельзя: у колонн и зеркала база высокая. */ for (c = b30 ? (int8_t)m->col : cend; c <= cend; c++) {
if (r >= 0 && r <= 2 && pop_tile_code(r, c)) dbg[2] |= 4; if (c < 0) continue;
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) &&
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) { pop_mob_overlay_tile(r, c, oref, (int8_t)m->col, m->x, m->draw_y, mob_spr[2]);
dbg[2] |= 0x10; dbg[3]++; if (rt != r && rt >= 0 && rt <= 2 && pop_tile_code(rt, c) &&
pop_mob_overlay_tile(r, c, r, (int8_t)m->col, m->x, m->draw_y, mob_spr[2]); ybot >= 63 * rt + 2 && ytop <= 63 * rt + 66)
} pop_mob_overlay_tile(rt, c, oref, (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) {
dbg[2] |= 0x80; dbg[3]++;
pop_mob_overlay_tile(rt, c, r, (int8_t)m->col, m->x, m->draw_y, mob_spr[2]);
} }
} }
@@ -1650,10 +1648,6 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
mob_t *m = &mobs[i]; mob_t *m = &mobs[i];
/* Отбор по draw_y, а НЕ по комнате: кусок, только что провалившийся /* Отбор по draw_y, а НЕ по комнате: кусок, только что провалившийся
* в комнату снизу, ещё виден из нашей (см. mob_tick_one). */ * в комнату снизу, ещё виден из нашей (см. 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 (!m->active || m->draw_y == MOB_Y_NONE) continue;
if ((uint8_t)(m->defer != 0) != want_defer) 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--) for (j = n; j > 0 && mobs[order[j - 1]].draw_y < m->draw_y; j--)
@@ -1670,10 +1664,8 @@ static void mob_draw_pass(uint8_t pg, uint8_t want_defer)
* в кадр пересечения границы пометка указывала на прежний тайл, и * в кадр пересечения границы пометка указывала на прежний тайл, и
* передняя часть колонны кусок не перекрывала — ровно ОДИН кадр, * передняя часть колонны кусок не перекрывала — ровно ОДИН кадр,
* как и наблюдалось (прогон 2026-08-13, комната 23, колонка 8). */ * как и наблюдалось (прогон 2026-08-13, комната 23, колонка 8). */
pop_dbg_pass[order[i]] |= 0x10;
mob_mark_neighbour(&mobs[order[i]]); mob_mark_neighbour(&mobs[order[i]]);
mob_render(&mobs[order[i]], pg); mob_render(&mobs[order[i]], pg);
pop_dbg_pass[order[i]] |= 0x20;
mob_overlay_neighbour(&mobs[order[i]]); mob_overlay_neighbour(&mobs[order[i]]);
} }
} }