Пики оставались навсегда: запечка фона консервировала выдвинутый кадр
Уровень 7, комната 19. Кнопка в (0,5) при нажатии зовёт pop_floor_bake, а тот перерисовывает и правого соседа — пики (0,6) — в банке, который пишет в ОЗУ-копию. Кадр брался живой, и выдвинутая пика оставалась в фоне: дальше heal возвращал её каждый кадр. В (0,7) чисто, потому что окно клипа запечки кончается на x=219, а колонка 7 начинается с 224. Флаг pop_t_bake_rest («в фон кладём покой») для этого и был, но читал его только pop_loose_frame. Кадр пик считался по месту в ЧЕТЫРЁХ слоях. Добавлен pop_spike_frame — порт get_spike_frame (SDLPoP seg008:08A0), которого у нас не было, — и все четыре слоя переведены на него; заодно позу покоя в запечке стал отдавать pop_chomp_pose. Одна точка лечит все пять путей запечки, включая вход в комнату. Резидент +28 Б, скорость не затронута. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2909,3 +2909,81 @@ case tiles_20_wall:
|
|||||||
после: изменения ровно в боксе `(344,199)-(358,206)`, 38 пикселей — сам
|
после: изменения ровно в боксе `(344,199)-(358,206)`, 38 пикселей — сам
|
||||||
артефакт и ничего кроме. На подземных уровнях изменений нет по построению:
|
артефакт и ничего кроме. На подземных уровнях изменений нет по построению:
|
||||||
полоска целиком под `if (PALACE)`.
|
полоска целиком под `if (PALACE)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="spike-baked"></a>
|
||||||
|
## SPIKE-BAKED. Выдвинутые пики консервировались в фон и оставались навсегда
|
||||||
|
|
||||||
|
**Наблюдение (пользователь, 2026-08-18).** Уровень 7, комната 19. Пики стоят
|
||||||
|
в `(0,6)` и из-за изометрии частью выходят в ячейку `(0,7)`. Выдвигаются
|
||||||
|
корректно все; **прячутся корректно только те, что в `(0,7)`**, а в `(0,6)`
|
||||||
|
остаются два вида мусора: куски остриёв на фоне дальней стены и белые точки в
|
||||||
|
дырах пола. Пользователь сразу указал и на класс («первоначальная отрисовка
|
||||||
|
фона, фикс не должен повлиять на скорость»), и на причину («в `(0,5)` не пол,
|
||||||
|
а кнопка») — оба попадания верные.
|
||||||
|
|
||||||
|
**Артефакты, снятые в MAME на живом кадре с мусором.**
|
||||||
|
|
||||||
|
| что | значение |
|
||||||
|
|-----|----------|
|
||||||
|
| `pop_t_bg[6]` (живой modif пики) | `0x00` — пики УБРАНЫ |
|
||||||
|
| `pop_t_fg[0..9]` | `03 04 13 0F 01 06 02 04 01 03` → `(0,5)` = `0x06` closer-кнопка, `(0,6)` = `0x02` spike |
|
||||||
|
| мусор на экране | x 198..215, y 63..88 |
|
||||||
|
|
||||||
|
Модификатор нулевой, а пиксели пик есть — значит рисует их не живое
|
||||||
|
состояние. И `pop_spike_redraw` каждый кадр делает `pop_heal_off(192, 62,
|
||||||
|
64, 40)` — ровно поверх этого прямоугольника. Мусор переживает СВОЙ ЖЕ heal,
|
||||||
|
а heal восстанавливает ОЗУ-копию фона: пики в ней и лежат.
|
||||||
|
|
||||||
|
**Корень.** Кнопка при смене состояния метится `POP_RD_FLOOR`, а это
|
||||||
|
`pop_floor_bake(0,5)` — ЗАПЕЧКА, то есть рисование в банке, который пишет и в
|
||||||
|
ОЗУ-копию. Запечка перерисовывает свой тайл **и правого соседа**:
|
||||||
|
|
||||||
|
```c
|
||||||
|
pop_t_bake_rest = 1;
|
||||||
|
draw_tile(row, col);
|
||||||
|
if (col + 1 < 10) draw_tile(row, col + 1); /* <- это пики (0,6) */
|
||||||
|
```
|
||||||
|
|
||||||
|
`pop_t_bake_rest` для того и заведён — «в фон кладём состояние ПОКОЯ», — но
|
||||||
|
читал его ТОЛЬКО `pop_loose_frame`. Кадр пик считался прямо по месту,
|
||||||
|
`(m & 0x80) ? 5 : m`, в ЧЕТЫРЁХ слоях, и ни один флага не спрашивал. Так
|
||||||
|
выдвинутая пика попадала в ОЗУ-копию, и дальше heal возвращал её вечно.
|
||||||
|
|
||||||
|
Всё сходится численно. Окно клипа запечки — `x 160..219` (ширина 60 от
|
||||||
|
кнопки), спрайт пики — `x 192..222`, `y 62..88`. Пересечение `x 192..219` —
|
||||||
|
это и есть наблюдаемые 198..215; а `(0,7)` начинается с 224, за окном
|
||||||
|
запечки, — потому там и чисто. Правая часть пики живёт только в видео-ОЗУ и
|
||||||
|
честно стирается.
|
||||||
|
|
||||||
|
**Фикс (`pop_tile.c`).** Порт `get_spike_frame` (SDLPoP seg008:08A0), которого
|
||||||
|
у нас не было, — плюс наша платформенная оговорка про запечку:
|
||||||
|
|
||||||
|
```c
|
||||||
|
uint8_t pop_spike_frame(uint8_t m)
|
||||||
|
{
|
||||||
|
if (pop_t_bake_rest) return 0;
|
||||||
|
return (uint8_t)((m & 0x80) ? 5 : m);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все четыре слоя (`draw_tile_anim`, `draw_tile_anim_right` соседа, fore-проход,
|
||||||
|
mid-оверлей) переведены на него. Заодно `pop_chomp_pose` тоже стал отдавать
|
||||||
|
позу покоя в запечке: у чомпера ровно та же схема, а рядом с кнопкой он
|
||||||
|
встретится так же легко.
|
||||||
|
|
||||||
|
Одна точка вместо четырёх лечит ВСЕ пять путей запечки сразу, включая
|
||||||
|
`pop_room_draw` — вход в комнату с выдвинутыми пиками тоже консервировал бы их.
|
||||||
|
|
||||||
|
**Цена.** Резидент +28 Б, банки без изменений; в кадре — один `call` на слой,
|
||||||
|
и только на тайлах-пиках. Скорость не затронута, как и предполагал
|
||||||
|
пользователь.
|
||||||
|
|
||||||
|
**Проверка.** Пользователь прошёл сценарий вручную на пересобранном билде —
|
||||||
|
мусора нет.
|
||||||
|
|
||||||
|
**Урок (записан в `pop_tile.h` над `pop_t_bake_rest`).** Флаг «сейчас
|
||||||
|
запечка» обязана спрашивать САМА функция «кадр по модификатору», а не
|
||||||
|
вызывающий слой: слоёв отрисовки у тайла четыре, и забыть один слишком легко.
|
||||||
|
Для новой анимированной ловушки это теперь обязательный пункт.
|
||||||
|
|||||||
@@ -873,6 +873,46 @@ tp/10 у факелов таблицей, пустой слот соперник
|
|||||||
|
|
||||||
## P1 — берётся в любой момент
|
## P1 — берётся в любой момент
|
||||||
|
|
||||||
|
### <a id="t-render"></a>T-RENDER. Тесты слоя отрисовки: «в запечку не попадает транзиент»
|
||||||
|
|
||||||
|
**Откуда взялось** (пользователь, 2026-08-18, по итогам
|
||||||
|
[SPIKE-BAKED](BUGS_CLOSED.md#spike-baked)): «такой сценарий (и другие с
|
||||||
|
пиками) можно внести в наши тесткейсы?»
|
||||||
|
|
||||||
|
**Что есть сейчас.** `tests-host/` — это ЛОГИКА: геометрия, физика Кида,
|
||||||
|
`Char`, спецсобытия. Отрисовки там нет вовсе (`stubs.c` глушит все блиты
|
||||||
|
пустышками), `pop_tile.c`/`pop_room.c`/`pop_bg.c` не линкуются — таблицы
|
||||||
|
деления на ширину тайла в стабах даже продублированы копией. **Ловушки не
|
||||||
|
покрыты ничем**: ни пик, ни чомперов, ни `animate_*`. То есть SPIKE-BAKED
|
||||||
|
тестами поймать было нельзя в принципе.
|
||||||
|
|
||||||
|
**Что покрывать.** Не «картинку» (её проверяет глаз в MAME), а инвариант,
|
||||||
|
на котором держится вся наша запечка:
|
||||||
|
|
||||||
|
> при `pop_t_bake_rest = 1` ни один слой отрисовки тайла не выдаёт
|
||||||
|
> ТРАНЗИЕНТНЫЙ спрайт — ни своей ячейки, ни соседней.
|
||||||
|
|
||||||
|
Он проверяем механически и стоит ровно того: именно его нарушение дало
|
||||||
|
SPIKE-BAKED, а до того — мерцание плиты, ради которого флаг и заводился.
|
||||||
|
|
||||||
|
**Как.** Новый набор `t_render`: линкуются НАСТОЯЩИЕ `pop_tile.c`,
|
||||||
|
`pop_room.c`, `pop_bg.c`, а `pop_env_b`/`pop_wall_b`/`pop_fore_b` подменяются
|
||||||
|
**записывающим** стабом — списком `(id, x, yb)` за вызов. Тогда проверка
|
||||||
|
пишется в одну строку: «`draw_tile` при `bake_rest=1` и modif пик 4 не выдал
|
||||||
|
ни одного id из `spikes_fram_*`». Тот же стаб сразу закрывает и чомпера, и
|
||||||
|
плиту, и будущие ловушки — по одному тесту на каждую.
|
||||||
|
|
||||||
|
Отдельно и дёшево — характеризация автоматов `animate_spike`/`animate_chomper`
|
||||||
|
(сверка полного цикла модификатора с SDLPoP seg007), но она НЕ ловит этот
|
||||||
|
класс: автомат был верен, врал слой отрисовки.
|
||||||
|
|
||||||
|
**Чего это стоит.** Не одна строка: набор тянет за собой атласы и
|
||||||
|
`pop_cd_*`, а `stubs.c` придётся разделить (сейчас он подменяет то, что
|
||||||
|
здесь нужно настоящим). Прикидка — отдельная сессия. Место в `DATA_LOC`
|
||||||
|
проверить заранее: набор линкует ещё три крупных модуля (см. грабли
|
||||||
|
раскладки в `tests-host/README.md`).
|
||||||
|
|
||||||
|
|
||||||
### <a id="qsave"></a>QSAVE. QuickSave / QuickLoad
|
### <a id="qsave"></a>QSAVE. QuickSave / QuickLoad
|
||||||
|
|
||||||
> **План целиком — [`../docs/quicksave_plan.md`](../docs/quicksave_plan.md)**
|
> **План целиком — [`../docs/quicksave_plan.md`](../docs/quicksave_plan.md)**
|
||||||
|
|||||||
@@ -593,8 +593,7 @@ static void fore_only_tile(int row, int col)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (code == 2) { /* пики: fore-кадр */
|
if (code == 2) { /* пики: fore-кадр */
|
||||||
uint8_t sm = pop_tile_mod(row, col);
|
uint8_t sf = pop_spike_frame(pop_tile_mod(row, col));
|
||||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
|
||||||
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
|
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
|
||||||
pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
|
pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
|
||||||
}
|
}
|
||||||
@@ -770,8 +769,7 @@ static void overlay_mid_tile(int row, int col)
|
|||||||
pop_env_b(base_id, x, t->base_y + dmy);
|
pop_env_b(base_id, x, t->base_y + dmy);
|
||||||
}
|
}
|
||||||
if (code == 2) { /* draw_tile_anim: свои пики */
|
if (code == 2) { /* draw_tile_anim: свои пики */
|
||||||
uint8_t sm = pop_tile_mod(row, col);
|
uint8_t sf = pop_spike_frame(pop_tile_mod(row, col));
|
||||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
|
||||||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||||||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ static void draw_tile(int row, int col)
|
|||||||
} else if (dt_lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
} else if (dt_lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||||
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[dt_row * 10 + (dt_col - 1)])], dt_x, dt_dby - 1);
|
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[dt_row * 10 + (dt_col - 1)])], dt_x, dt_dby - 1);
|
||||||
} else if (dt_lcode == 2) { /* spike: статич. кадр */
|
} else if (dt_lcode == 2) { /* spike: статич. кадр */
|
||||||
uint8_t sf = (dt_lmod & 0x80) ? 5 : dt_lmod;
|
uint8_t sf = pop_spike_frame(dt_lmod);
|
||||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||||
pop_env_b(SPIKES_FRAM_RIGHT[sf], dt_x, dt_dmy - 7);
|
pop_env_b(SPIKES_FRAM_RIGHT[sf], dt_x, dt_dmy - 7);
|
||||||
} else if (dt_lcode == 16) { /* дверь уровня */
|
} else if (dt_lcode == 16) { /* дверь уровня */
|
||||||
@@ -551,8 +551,7 @@ static void draw_tile(int row, int col)
|
|||||||
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||||||
* пустая. */
|
* пустая. */
|
||||||
if (dt_row >= 0 && dt_code == 2) {
|
if (dt_row >= 0 && dt_code == 2) {
|
||||||
uint8_t sm = pop_tile_mod(dt_row, dt_col);
|
uint8_t sf = pop_spike_frame(pop_tile_mod(dt_row, dt_col));
|
||||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
|
||||||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||||||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], dt_x, dt_dmy - 2);
|
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], dt_x, dt_dmy - 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,11 @@ const uint8_t POP_CHOMP_FRAM_FOR[5] = {106, 107, 108, 109, 110};
|
|||||||
uint8_t pop_chomp_pose(uint8_t modif)
|
uint8_t pop_chomp_pose(uint8_t modif)
|
||||||
{
|
{
|
||||||
uint8_t m = (uint8_t)(modif & 0x7F);
|
uint8_t m = (uint8_t)(modif & 0x7F);
|
||||||
if (m > 6) m = 6;
|
/* В ЗАПЕЧКЕ — поза ПОКОЯ, по той же причине, что у плиты и пик: фаза
|
||||||
|
* челюстей транзиентна. Покой и есть фаза 0 (она же 6, на которой
|
||||||
|
* animate_chomper снимает trob): CHOMP_FRAM1[0] == CHOMP_FRAM1[6]. */
|
||||||
|
if (pop_t_bake_rest) m = 0;
|
||||||
|
else if (m > 6) m = 6;
|
||||||
return CHOMP_FRAM1[m];
|
return CHOMP_FRAM1[m];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +171,20 @@ uint8_t pop_loose_frame(uint8_t m)
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Кадр пик по их модификатору: 1..4 — выдвижение, 0x80|n — «вышли целиком»
|
||||||
|
* (один кадр 5), 6..8 — уход. Как и у плиты, в режиме ЗАПЕЧКИ отдаёт кадр
|
||||||
|
* ПОКОЯ (0 = пик не видно): выдвинутая пика — транзиент, её место в ОЗУ-копии
|
||||||
|
* фона делает heal вечным (BUG-SPIKE-BAKED, см. BUGS_CLOSED.md).
|
||||||
|
*
|
||||||
|
* Отдельная функция, а не `(m & 0x80) ? 5 : m` по месту: считают кадр ЧЕТЫРЕ
|
||||||
|
* слоя (draw_tile_anim, draw_tile_anim_right соседа, fore-проход и
|
||||||
|
* mid-оверлей), и одного забытого хватило, чтобы пики законсервировались. */
|
||||||
|
uint8_t pop_spike_frame(uint8_t m)
|
||||||
|
{
|
||||||
|
if (pop_t_bake_rest) return 0;
|
||||||
|
return (uint8_t)((m & 0x80) ? 5 : m);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- *
|
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- *
|
||||||
*
|
*
|
||||||
* Метка — БИТОВАЯ МАСКА ТАЙЛОВ, по слову на ряд и по набору на страницу.
|
* Метка — БИТОВАЯ МАСКА ТАЙЛОВ, по слову на ряд и по набору на страницу.
|
||||||
|
|||||||
@@ -121,16 +121,27 @@ extern const uint8_t POP_LOOSE_FRAM_BOTTOM[12]; /* низ дрожащей пл
|
|||||||
extern const uint8_t POP_CHOMP_FRAM_FOR[5]; /* чомпер: перед (pop_fore!) */
|
extern const uint8_t POP_CHOMP_FRAM_FOR[5]; /* чомпер: перед (pop_fore!) */
|
||||||
|
|
||||||
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
||||||
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
* из которой потом восстанавливает heal). В этом режиме ТРАНЗИЕНТНЫЕ кадры
|
||||||
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
* рисовать НЕЛЬЗЯ — ни свои, ни соседские: кадр законсервируется в копии, и
|
||||||
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
* heal будет возвращать его вечно, даже когда ловушка давно вернулась в
|
||||||
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Фон обязан быть
|
* покой (пики: BUG-SPIKE-BAKED). А так как запечка идёт двумя кадрами (по
|
||||||
* состоянием ПОКОЯ; анимация каждый кадр кладётся поверх (банк SPRITE). */
|
* одному на страницу дабл-буфера), на страницах могут застыть ещё и РАЗНЫЕ
|
||||||
|
* кадры — вечное мерцание. Фон обязан быть состоянием ПОКОЯ; анимация
|
||||||
|
* каждый кадр кладётся поверх (банк SPRITE).
|
||||||
|
*
|
||||||
|
* Флаг читают ВСЕ функции «кадр по модификатору»: pop_loose_frame,
|
||||||
|
* pop_spike_frame, pop_chomp_pose. Появится новая анимированная ловушка —
|
||||||
|
* её кадр обязан спрашивать флаг ТАМ ЖЕ, а не в вызывающем слое: слоёв
|
||||||
|
* отрисовки у тайла четыре, и забыть один слишком легко. */
|
||||||
extern uint8_t pop_t_bake_rest;
|
extern uint8_t pop_t_bake_rest;
|
||||||
|
|
||||||
/* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */
|
/* Кадр дрожащей/падающей плиты по её room_modif (seg007 loose_fram_*). */
|
||||||
uint8_t pop_loose_frame(uint8_t modif);
|
uint8_t pop_loose_frame(uint8_t modif);
|
||||||
|
|
||||||
|
/* Кадр пик по их modif (индекс в spikes_fram_*): 0 = не видно, 5 = вышли
|
||||||
|
* целиком. В режиме запечки — всегда 0 (см. pop_t_bake_rest). */
|
||||||
|
uint8_t pop_spike_frame(uint8_t modif);
|
||||||
|
|
||||||
/* Номер позы чомпера по его модификатору (seg008 chomper_fram1): 0..4 —
|
/* Номер позы чомпера по его модификатору (seg008 chomper_fram1): 0..4 —
|
||||||
* индекс во ВСЕ три набора кусков (низ/верх/перед) и в кровь (+114/+119). */
|
* индекс во ВСЕ три набора кусков (низ/верх/перед) и в кровь (+114/+119). */
|
||||||
uint8_t pop_chomp_pose(uint8_t modif);
|
uint8_t pop_chomp_pose(uint8_t modif);
|
||||||
|
|||||||
Reference in New Issue
Block a user