Уровень 4: skip по маске тайлов + порт loose_land, ворота 0xFF, пламя в фон
ОПТИМИЗАЦИЯ. Метка «фон трогали» была одним union-прямоугольником на страницу, и три факела комнаты склеивались в полосу x 40..280 на всю комнату — Кид, стоящий между крайними факелами, терял пропуск перерисовки и каждый кадр платил полным fore-проходом. Теперь это маска тайлов (uint16_t pop_cd_dmask[2][3]: бит = колонка, слово = ряд, набор = страница), проверка — три AND через резидентный pop_cd_hit. Гранулярность тайла — это гранулярность оригинала (redraw_frames_anim[tilepos], set_wipe; подтайловое уточнение там только по высоте, wipe_heights). Замер на (1,7): циан 233 515 -> 24 781, работа за кадр 517 609 -> 306 553, период 4 -> 3 растровых кадра. BUG-LOOSE-BUTTON-1. Упавшая плита не нажимала кнопку. Три слоя: порт loose_land не звал trigger_button вовсе; pop_room_col_landing считала площадкой только чистый пол, а у оригинала их семь (пол, пика, обе кнопки, зелье, оба факела); сигнал приходил в момент ОТРЫВА плиты, из-за чего ворота начинали открываться, пока она ещё в воздухе. Нажатие в оригинале ОДНО, но с button_type = tiles_14_debris — это «открыть НАСОВСЕМ» (modifier 0xFF), и кнопка съедается. Посадка в комнате снизу переехала на новый сигнал pop_loose_exit (взводит mob_tick_one, когда кусок ушёл ниже поля). BUG-GATE-FF-1. 0xFF был перегружен: сторожевое «тайла нет» в gate_modif и живое «открыто навсегда» из trigger_gate. gate_passable заворачивал Кида в воротах, нарисованных открытыми. Мёртвая ветка убрана. BUG-TORCH-CHOMP-1. Чомпер (0,7) комнаты 23 healит x 224..255 / y 30..93 и стирал пламя факела (0,6), которое рисуется в ячейке правого соседа. Фикс — запекать пламя (GFX_BANK_NORMAL): у факела все девять кадров на общем канвасе 16x18 и непрозрачны, протухнуть в ОЗУ-копии нечему, а heal чомпера сам возвращает огонь и кладёт челюсти поверх — z-порядок как в оригинале. Пузырёк зелья так нельзя (ползёт вверх, нужен heal) — остаётся в SPRITE. Заведено открытым: died_on_button (seg007:776) не портирован — нужен тайл tiles_5_stuck в атласе. Проверено пользователем в MAME (уровень 4: плита 16(1,1) -> кнопка 17(0,1) -> ворота 23(0,9); комната 23 с чомпером и факелом); make -C tests-host — все 5 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,34 @@ MAME (`z80_profiling_method`) на роомтесте, уровень 1 комн
|
||||
139 398 (27 %), циан (`PROF(6)`) 233 515 (45 % работы = 54 % растрового
|
||||
кадра, начинается на 74 % первого кадра и кончается на 123 %).
|
||||
|
||||
## СДЕЛАНО 2026-08-10: метка «фон трогали» стала маской ТАЙЛОВ
|
||||
|
||||
Было: один union-прямоугольник на страницу. Три факела трогают по пятну
|
||||
16x18 в колонках 1, 6 и 8, а их объединение — полоса `x 40..280` на всю
|
||||
комнату; Кид, стоящий где угодно между крайними факелами, в неё попадал и
|
||||
перерисовывался каждый кадр со всем fore-проходом.
|
||||
|
||||
Стало: `uint16_t pop_cd_dmask[2][3]` — бит на колонку, слово на ряд, набор на
|
||||
страницу. Колонка берётся сдвигом (`x >> 5`), ряд — цепочкой сравнений;
|
||||
проверка в `cd_quiet` — три `AND` через резидентный `pop_cd_hit`.
|
||||
|
||||
Гранулярность тайла — это гранулярность ОРИГИНАЛА: пометки там тоже по
|
||||
тайлам (`redraw_frames_anim[tilepos]`, `set_wipe`), персонажи привязаны к
|
||||
тайлу через `tile_object_redraw[tilepos]`, а единственное подтайловое
|
||||
уточнение (`wipe_heights`) — по высоте, не по ширине. Полутайл (16 px) не дал
|
||||
бы ничего: пламя рисуется с отступом 8 px и шириной 16, то есть занимает
|
||||
середину тайла и задевает обе половины.
|
||||
|
||||
Замер на той же сцене, где снимался профиль ниже (Кид неподвижно на (1,7)):
|
||||
|
||||
| | было | стало |
|
||||
|---|---:|---:|
|
||||
| циан (спрайты + fore) | 233 515 | **24 781** |
|
||||
| работа за кадр | 517 609 | **306 553** |
|
||||
| период | 4 растровых кадра | **3** |
|
||||
|
||||
---
|
||||
|
||||
## 0. Дворцовая кладка в fore-проходе — 27 % кадра НА НОЛЬ ПИКСЕЛЕЙ
|
||||
|
||||
**Цена: 139 863 такта за кадр (32 % растрового кадра), полезных пикселей —
|
||||
|
||||
@@ -13,6 +13,96 @@
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-loose-button-1"></a>
|
||||
## BUG-LOOSE-BUTTON-1. Упавшая плита не нажимает кнопку — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
**Симптом (пользователь, уровень 4).** Плита `16(1,1)` падает вниз, в комнате
|
||||
17 ничего не меняется: кнопка цела, щебня нет, ворота не открылись.
|
||||
|
||||
**Корень — три слоя.**
|
||||
|
||||
1. Порт `loose_land` (seg007:11E8) у нас вообще не звал `trigger_button`: и в
|
||||
своей комнате (`pop_map.c`), и в комнате снизу (`roomtest.c`) он только
|
||||
клал щебень.
|
||||
2. `pop_room_col_landing` (pop_level.c) считала площадкой **только чистый
|
||||
пол** (`t == 1`). У оригинала площадок семь — `tiles_1_floor`,
|
||||
`tiles_2_spike`, `tiles_6_closer`, `tiles_10_potion`, `tiles_15_opener`,
|
||||
`tiles_19_torch`, `tiles_30_torch_with_debris`; на всём прочем кусок просто
|
||||
исчезает. Кнопка в набор не входила, поэтому плита растворялась.
|
||||
3. Сигнал `pop_loose_fell` взводится в момент ОТРЫВА плиты, а по нему главный
|
||||
цикл и щебень клал, и кнопку жал — ворота начинали открываться, пока плита
|
||||
ещё в воздухе.
|
||||
|
||||
**Механика оригинала (важно, интуиция обманывает).** Плита не «постоянно
|
||||
давит» на кнопку. Нажатие ОДНО, но с `button_type = tiles_14_debris`, а это у
|
||||
`trigger_gate` отдельная ветка: возврат 2 → `animate_door` доводит створку и
|
||||
ставит `curr_modifier = 0xFF` → ворота заморожены открытыми навсегда, обычный
|
||||
opener их больше не трогает (`if (modifier == 0xFF) return -1`). Сама кнопка
|
||||
съедается: тайл становится щебнем. На closer — быстрое закрытие и тоже
|
||||
щебень. На факеле остаётся отдельный тайл `tiles_30_torch_with_debris`.
|
||||
|
||||
**Фикс.** Набор площадок расширен до оригинального; `pop_trigger_button`
|
||||
зовётся в обоих местах с правильным `button_type`; посадка в комнате снизу
|
||||
переехала с `pop_loose_fell` на новый сигнал `pop_loose_exit`, который
|
||||
`mob_tick_one` взводит, когда кусок ушёл ниже поля комнаты.
|
||||
|
||||
**Осознанное расхождение.** Оригинал через `mob_down_a_row` продолжает
|
||||
симуляцию куска уже в нижней комнате; у нас он там не летит, а садится сразу.
|
||||
Задержка получается «высота комнаты», а не «высота комнаты плюс путь до пола
|
||||
внизу».
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-gate-ff-1"></a>
|
||||
## BUG-GATE-FF-1. Ворота, открытые навсегда, нарисованы открытыми, но непроходимы — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
**Симптом.** После BUG-LOOSE-BUTTON-1 ворота `23(0,9)` рисуются поднятыми, но
|
||||
Кид в них упирается.
|
||||
|
||||
**Корень — перегруженное значение.** `gate_modif` (pop_map.c) отдаёт `0xFF`
|
||||
как сторожевое «тайла нет за краем уровня», а `trigger_gate` ставит тот же
|
||||
`0xFF` как живой модификатор «открыто НАСОВСЕМ». В `gate_passable` стояло
|
||||
`if (gm == 0xFF) return 0;`. До сегодняшнего дня значение в игре не
|
||||
возникало — ветку по щебню никто не запускал.
|
||||
|
||||
**Фикс.** Ветка убрана: она мёртвая (нас зовут только когда тайл ТОЧНО
|
||||
ворота), а `(0xFF >> 2) + 6 = 69` и так больше любого `fph`. Остальные
|
||||
читатели `gate_modif` трактуют 63 как «открыто» и правок не потребовали.
|
||||
|
||||
---
|
||||
|
||||
<a id="bug-torch-chomp-1"></a>
|
||||
## BUG-TORCH-CHOMP-1. Чомпер стирает пламя соседнего факела — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
**Симптом (пользователь).** Комната 23 уровня 4: факел есть, огня нет.
|
||||
|
||||
**Корень.** Пламя рисуется в ячейке ПРАВОГО соседа (`x = COL_XH[col+1]*8 + 8`
|
||||
= 232..248, `y` 33..50), то есть в тайле чомпера (0,7). `pop_chomp_redraw`
|
||||
начинается с `pop_heal_off(224, 2, 32, 64)` — восстанавливает запечённый фон в
|
||||
`x 224..255, y 30..93`. Пламя в фон не запекалось и рисовалось в
|
||||
`GFX_BANK_SPRITE`, поэтому heal его съедал; вдобавок `pop_redraw_needed` идёт
|
||||
ПОСЛЕ `pop_process_trobs`, так что стиралось только что нарисованное.
|
||||
|
||||
**Тупик, в который я сначала свернул.** Вынес отрисовку пламени в отдельный
|
||||
проход после `pop_redraw_needed` — стало хуже: пламя легло ПОВЕРХ челюстей
|
||||
(правильный z-порядок существует только внутри общего обхода тайлов, где
|
||||
`draw_tile_anim_right` идёт до `draw_tile_anim`), плюс проход индексировал
|
||||
`trob_code[i]` параллельно `trobs[i]`, а `pop_process_trobs` в конце уплотняет
|
||||
массив — индексы разъезжались, и чужой trob рисовался как факел (пламя из
|
||||
головы Кида). Проход откачен.
|
||||
|
||||
**Фикс (подсказан пользователем).** Пламя ЗАПЕКАЕТСЯ: `pop_torch_draw` пишет
|
||||
в `GFX_BANK_NORMAL` (видео-ОЗУ + ОЗУ-копия). Это безопасно именно у факела —
|
||||
все девять кадров лежат на общем канвасе 16×18 и упакованы `opaque=True`, то
|
||||
есть кадр полностью накрывает предыдущий, протухнуть в копии нечему; и факел
|
||||
всегда задний план. Тогда heal чомпера возвращает огонь сам, а челюсти
|
||||
ложатся поверх — тот же z-порядок, что в оригинале, и без лишнего прохода.
|
||||
|
||||
Пузырёк ЗЕЛЬЯ так нельзя: он ползёт вверх, себя не накрывает и требует heal —
|
||||
`pop_potion_draw` остаётся в `GFX_BANK_SPRITE`. Столкнуться с чомпером в
|
||||
одном тайле зелье не может.
|
||||
|
||||
|
||||
<a id="bug-guard-ix-1"></a>
|
||||
## BUG-GUARD-IX-1. «Зависание» при бое со стражем — затёртый IX главного цикла — **ЗАКРЫТ 2026-08-10**
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
| ~~BUG-GATE-PASS-1~~ | проход сквозь закрытую решётку шва | Major | **ЗАКРЫТ 2026-08-09** — [bug_closed.md](bug_closed.md#bug-gate-pass-1), смоук уровня 1 пройден |
|
||||
| ~~BUG-GUARD-IX-1~~ | «зависание» в бою со стражем: затёрт IX главного цикла | Blocker | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-guard-ix-1) |
|
||||
| ~~BUG-GATE-SEAM-ROW1~~ | решётка в шве не анимируется, если ворота не в ряду 0 | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-gate-seam-row1) |
|
||||
| ~~BUG-LOOSE-BUTTON-1~~ | упавшая плита не нажимает кнопку под собой | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-loose-button-1) |
|
||||
| ~~BUG-GATE-FF-1~~ | ворота «открыты навсегда» непроходимы (0xFF перегружен) | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-gate-ff-1) |
|
||||
| ~~BUG-TORCH-CHOMP-1~~ | чомпер стирает пламя соседнего факела | Minor | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-torch-chomp-1) |
|
||||
| [DIED-ON-BUTTON](#died-on-button) | `died_on_button` (seg007:776) не портирован | порт | открыт |
|
||||
|
||||
---
|
||||
|
||||
@@ -310,3 +314,30 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал
|
||||
- Комнаты **13, 18, 24 уровня 1 недостижимы** в обычной игре — это свойство
|
||||
данных уровня (разбор — «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md));
|
||||
приоритет багов в них низкий. Аналогично 23/24 на уровне 3.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<a id="died-on-button"></a>
|
||||
## DIED-ON-BUTTON. `died_on_button` (seg007:776) не портирован
|
||||
|
||||
Обнаружено при разборе [BUG-LOOSE-BUTTON-1](bug_closed.md#bug-loose-button-1).
|
||||
`check_press` (seg006:1707) разбирает ЛЮБОГО мёртвого `Char`, не только Кида:
|
||||
|
||||
```c
|
||||
if (curr_tile2 == tiles_15_opener || curr_tile2 == tiles_6_closer) {
|
||||
if (Char.alive < 0) trigger_button(1, 0, -1); /* жив */
|
||||
else died_on_button(); /* мёртв */
|
||||
}
|
||||
```
|
||||
|
||||
`died_on_button` на **opener** делает тайл обычным полом и форсирует
|
||||
`button_type = tiles_14_debris` — ворота открываются насовсем; на любой другой
|
||||
кнопке ставит `tiles_5_stuck` (заклинена, `link_timer == 0x1F`, связь мертва).
|
||||
|
||||
У Кида эффект живёт до перезапуска уровня (смерть → `is_restart_level` →
|
||||
`play_level` заново зовёт `load_level()`, тайлы перечитываются), а вот когда на
|
||||
кнопке умирает СТРАЖ — перезапуска нет, и кнопка заклинена до конца уровня.
|
||||
|
||||
Что нужно: сам порт `died_on_button`, константа `TILE_STUCK = 5` и тайл
|
||||
заклиненной кнопки в атласе фона (сейчас его там нет).
|
||||
|
||||
@@ -235,8 +235,8 @@ static uint8_t cd_quiet(uint8_t who, uint8_t p)
|
||||
int x0, y0, x1, y1;
|
||||
cd_bbox(who, p, &x0, &y0, &x1, &y1);
|
||||
y0 += POP_YOFF; y1 += POP_YOFF; /* метка фона — в ЭКРАННЫХ */
|
||||
if (x0 < pop_cd_dx1[p] && pop_cd_dx0[p] < x1 &&
|
||||
y0 < pop_cd_dy1[p] && pop_cd_dy0[p] < y1) return 0;
|
||||
/* cd_bbox отдаёт x1/y1 ИСКЛЮЧИТЕЛЬНО, pop_cd_hit ждёт включительно. */
|
||||
if (pop_cd_hit(p, x0, y0, x1 - 1, y1 - 1)) return 0;
|
||||
}
|
||||
cd_sig_make(who, &g);
|
||||
a = (const uint8_t *)&g; b = (const uint8_t *)&cd_sig[who][p];
|
||||
|
||||
@@ -120,8 +120,12 @@ void pop_char_set_render_dx(uint8_t who, int dx) __banked;
|
||||
*
|
||||
* Резидент (pop_tile.c): зовёт и банковый слой фона, и резидентные листья,
|
||||
* а трамплин на КАЖДЫЙ кусок фона стоил бы дороже самой метки. */
|
||||
extern uint8_t pop_cd_dirty; /* биты страниц: прямоугольник взведён */
|
||||
extern int pop_cd_dx0[2], pop_cd_dy0[2], pop_cd_dx1[2], pop_cd_dy1[2];
|
||||
extern uint8_t pop_cd_dirty; /* биты страниц: метка взведена */
|
||||
extern uint16_t pop_cd_dmask[2][3]; /* [страница][ряд] = биты колонок 0..9 */
|
||||
/* Задели ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 включительно) то,
|
||||
* что трогали на странице p. Резидент (pop_tile.c). */
|
||||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1);
|
||||
void pop_cd_clear(uint8_t p); /* снять метку страницы целиком */
|
||||
void pop_cd_touch(int x, int y, int w, int h);
|
||||
#define POP_CD_TOUCH_ALL() pop_cd_touch(0, 0, 320, 256)
|
||||
|
||||
|
||||
@@ -243,7 +243,14 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col)
|
||||
for (row = 0; row < 3; row++) { /* кусок пролетает пустые ряды */
|
||||
uint8_t t = (uint8_t)(rf[row * 10 + col] & 0x1F);
|
||||
if (t == 0) continue; /* empty — дальше вниз */
|
||||
if (t == 1) res = row; /* floor — приземление → debris */
|
||||
/* Набор площадок — дословно switch из loose_land (seg007:11E8):
|
||||
* пол, пика, обе кнопки, зелье и оба факела. На всём остальном
|
||||
* (стена, ворота, колонна) кусок просто исчезает. Раньше здесь
|
||||
* стоял только пол, и плита, падавшая на КНОПКУ в комнате снизу,
|
||||
* пропадала бесследно: ни щебня, ни нажатия (уровень 4, плита
|
||||
* 16(1,1) -> кнопка 17(0,1), открывающая ворота 23(0,9)). */
|
||||
if (t == 1 || t == 2 || t == 6 || t == 10 ||
|
||||
t == 15 || t == 19 || t == 30) res = row;
|
||||
break; /* первый не-пустой тайл — стоп */
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
#define TILE_WALL 20
|
||||
#define TILE_LOOSE 11 /* проваливающийся пол */
|
||||
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
||||
#define TILE_TORCH 19 /* факел */
|
||||
#define TILE_TORCH_DEBRIS 30 /* факел + щебень (loose_land) */
|
||||
#define TILE_SPIKE 2 /* пики */
|
||||
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
|
||||
#define TILE_POTION 10 /* зелье */
|
||||
@@ -319,7 +321,14 @@ uint8_t pop_gate_modif(int8_t col, int8_t row) __banked
|
||||
static uint8_t gate_passable(int8_t col, int8_t row)
|
||||
{
|
||||
uint8_t gm = gate_modif(col, row);
|
||||
if (gm == 0xFF) return 0;
|
||||
/* Ветки `if (gm == 0xFF) return 0;` здесь БЫТЬ НЕ ДОЛЖНО: 0xFF — это не
|
||||
* только сторожевое «тайла нет» из gate_modif, но и живой модификатор
|
||||
* «ворота открыты НАСОВСЕМ» (trigger_gate по button_type = щебень,
|
||||
* seg007:623). Значение это появилось только когда заработал порт
|
||||
* loose_land, и ворота выходили нарисованными открытыми (отрисовка
|
||||
* клампит modl>188 -> 188), но непроходимыми. Сторож сюда не приходит:
|
||||
* зовут нас только там, где тайл ТОЧНО ворота, а (0xFF>>2)+6 = 69 и так
|
||||
* больше любого fph. */
|
||||
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)CD_ACT->fph;
|
||||
}
|
||||
|
||||
@@ -2126,9 +2135,24 @@ void pop_loose_tick(void) __banked
|
||||
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
|
||||
uint8_t pos = (uint8_t)(pop_loose_landed - 1);
|
||||
uint8_t r = 0, c = pos;
|
||||
uint8_t tt = (uint8_t)(g_fg[pos] & 0x1F); /* тайл, НА который села плита */
|
||||
pop_loose_landed = 0;
|
||||
while (c >= 10) { c -= 10; r++; }
|
||||
g_fg[pos] = TILE_DEBRIS;
|
||||
/* Плита, севшая на КНОПКУ, её нажимает (seg007:11E8, ветки
|
||||
* tiles_15_opener / tiles_6_closer). Тонкость: на opener оригинал
|
||||
* подставляет button_type = tiles_14_debris, а это у trigger_gate
|
||||
* отдельная ветка «открыть НАСОВСЕМ» (возврат 2 -> animate_door
|
||||
* доводит створку и ставит modifier 0xFF, после чего ворота
|
||||
* заморожены и обычный opener их больше не трогает). Сама кнопка
|
||||
* съедается: тайл станет щебнем ниже, нажаться второй раз нечему.
|
||||
* Модификатор (индекс LINKLOC) тайл не меняет, берём его как есть. */
|
||||
if (tt == TILE_OPENER || tt == TILE_CLOSER)
|
||||
pop_trigger_button(g_room, pos,
|
||||
(uint8_t)(tt == TILE_OPENER ? TILE_DEBRIS : tt),
|
||||
pop_trob_modif(g_room)[pos]);
|
||||
/* На факеле — отдельный тайл «факел с щебнем» (seg007:1067). */
|
||||
g_fg[pos] = (uint8_t)((tt == TILE_TORCH || tt == TILE_TORCH_DEBRIS)
|
||||
? TILE_TORCH_DEBRIS : TILE_DEBRIS);
|
||||
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не LOOSE_GONE: та стирает и верх стены
|
||||
* ряда ниже — оставался чёрный прямоугольник под тайлом). Обе
|
||||
* страницы дабл-буфера. */
|
||||
|
||||
@@ -355,9 +355,11 @@ static void draw_tile(int row, int col)
|
||||
} else if (lcode == 16) { /* дверь уровня */
|
||||
draw_leveldoor(lmod, xh, dmy);
|
||||
}
|
||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
||||
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
||||
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
|
||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) здесь, в статичной
|
||||
* отрисовке тайла, не рисуем — его ведёт pop_torch_draw из
|
||||
* pop_process_trobs каждый кадр. Но ЗАПЕКАЕТСЯ оно всё равно (там
|
||||
* GFX_BANK_NORMAL): факел — чистый задний план, а самонакрытие кадров
|
||||
* делает запекание безопасным. Подробности — в шапке pop_torch_draw. */
|
||||
}
|
||||
|
||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||
@@ -608,8 +610,22 @@ void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
||||
/* heal НЕ нужен: оригинал рисует пламя blitters_0_no_transp (seg008
|
||||
* draw_tile_anim_right), и все 9 кадров лежат на общем канвасе 16x18 —
|
||||
* непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры
|
||||
* упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS). */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
* упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS).
|
||||
*
|
||||
* И ровно поэтому пламя ЗАПЕКАЕТСЯ (GFX_BANK_NORMAL: видео-ОЗУ + ОЗУ-копия),
|
||||
* а не рисуется в SPRITE поверх фона. Запечь его безопасно именно из-за
|
||||
* самонакрытия — следующий кадр сотрёт предыдущий сам, «протухнуть» в
|
||||
* копии нечему. А выигрыш принципиальный: точечные перерисовки соседних
|
||||
* тайлов начинаются с heal из ОЗУ-копии, и незапечённое пламя они стирали
|
||||
* — чомпер (0,7) комнаты 23 уровня 4 healит x 224..255 / y 30..93 и
|
||||
* съедал факел (0,6), который рисуется в ячейке правого соседа. С
|
||||
* запеканием heal возвращает огонь сам, а челюсти ложатся ПОВЕРХ него —
|
||||
* тот же z-порядок, что у оригинала, где draw_tile_anim_right (пламя
|
||||
* левого соседа) идёт до draw_tile_anim (сам чомпер).
|
||||
*
|
||||
* Пузырёк ЗЕЛЬЯ так нельзя: он ползёт вверх и себя не накрывает, ему
|
||||
* нужен heal — pop_potion_draw остаётся в SPRITE. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_pot_b((uint8_t)(modif + 1), x, yb);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
@@ -889,6 +905,13 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||
}
|
||||
}
|
||||
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
|
||||
/* Приземление в комнате СНИЗУ разбирает главный цикл — но ИМЕННО
|
||||
* сейчас, когда кусок долетел, а не в момент отрыва плиты: иначе
|
||||
* кнопка внизу срабатывала, пока плита ещё в воздухе. */
|
||||
if (!pop_loose_exit) {
|
||||
pop_loose_exit = (uint8_t)(m->col + 1);
|
||||
pop_loose_exit_room = m->room;
|
||||
}
|
||||
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
#include "pop_ctrl.h" /* объявления шины control_* (живёт здесь) */
|
||||
|
||||
uint8_t pop_loose_landed;
|
||||
/* Кусок loose УШЁЛ ВНИЗ из комнаты (порт хвоста move_loose, seg007:1126:
|
||||
* mob_down_a_row переносит его в комнату снизу; у нас симуляция там не
|
||||
* продолжается, и приземление разбирает главный цикл). 0 = нет, иначе
|
||||
* col+1; комната, ИЗ которой он ушёл, — в pop_loose_exit_room.
|
||||
* Сигнал отдельный от pop_loose_fell: тот взводится в момент ОТРЫВА плиты
|
||||
* (там верен только remove_loose), и по нему кнопка внизу нажималась,
|
||||
* пока кусок ещё летел. */
|
||||
uint8_t pop_loose_exit;
|
||||
uint8_t pop_loose_exit_room;
|
||||
uint8_t pop_droppedout;
|
||||
/* leveldoor_open (seg007:456): дверь уровня доехала до верха. Ставит
|
||||
* анимация двери (pop_trob, банк 6), читает спецсобытие подъёма скелета
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
* разбирает логика loose-полов (pop_map, W1/W2). */
|
||||
extern uint8_t pop_loose_landed;
|
||||
|
||||
/* Кусок loose ушёл ниже поля своей комнаты: 0 = нет, иначе col+1; комната
|
||||
* отправления — pop_loose_exit_room. Ставит отрисовка mob (банк 7),
|
||||
* разбирает главный цикл (посадка в комнате снизу). */
|
||||
extern uint8_t pop_loose_exit;
|
||||
extern uint8_t pop_loose_exit_room;
|
||||
|
||||
/* Чит «бессмертие» (I): Кида бьют, но HP не убавляется. Читают пути урона
|
||||
* (боёвка в банке, пики в pop_map), переключает главный цикл. */
|
||||
extern uint8_t pop_immortal;
|
||||
|
||||
@@ -158,40 +158,104 @@ uint8_t pop_loose_frame(uint8_t m)
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- */
|
||||
uint8_t pop_cd_dirty;
|
||||
int pop_cd_dx0[2], pop_cd_dy0[2], pop_cd_dx1[2], pop_cd_dy1[2];
|
||||
/* ---- «Фон трогали ВОТ ЗДЕСЬ» (DRAW-COST; контракт — pop_cdraw.h) ----- *
|
||||
*
|
||||
* Метка — БИТОВАЯ МАСКА ТАЙЛОВ, по слову на ряд и по набору на страницу.
|
||||
* Раньше здесь лежал ОДИН union-прямоугольник на страницу, и это убивало
|
||||
* пропуск перерисовки персонажа: три факела комнаты трогают по пятнышку
|
||||
* 16x18 в колонках 1, 6 и 8, а их объединение — полоса x 40..280 на всю
|
||||
* комнату. Кид, стоящий где угодно между крайними факелами, попадал в неё
|
||||
* и перерисовывался каждый кадр со всем fore-проходом (замер: циан 231 829
|
||||
* тактов против 20 071, период 4 растровых кадра против 3).
|
||||
*
|
||||
* Гранулярность тайла (32 x 63) вместо точного прямоугольника — осознанное
|
||||
* огрубление: факел помечает всю свою колонку по высоте ряда. Персонаж и
|
||||
* так занимает бОльшую часть высоты ряда, зато касания перестают
|
||||
* склеиваться. См. docs/impl_diff.md. */
|
||||
uint8_t pop_cd_dirty;
|
||||
uint16_t pop_cd_dmask[2][3];
|
||||
|
||||
/* Цикл по страницам развёрнут, а x+w / y+h посчитаны один раз. Не
|
||||
* вкусовщина: индекс-переменная заставляла SDCC считать адрес каждой из
|
||||
* восьми ячеек сложением, а `x + w` в условии и в присваивании считались
|
||||
* дважды — замер в MAME дал 6 846 тактов на вызов, а зовётся он с КАЖДОГО
|
||||
* блита фона (два факела в комнате — уже 13 700 за кадр). */
|
||||
/* CD_LOW[n] = n младших единиц: маска пробега колонок c0..c1 считается как
|
||||
* CD_LOW[c1+1] & ~CD_LOW[c0] — два чтения таблицы вместо цикла сдвигов. */
|
||||
static const uint16_t CD_LOW[11] = {
|
||||
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF
|
||||
};
|
||||
|
||||
/* Экранный y -> ряд комнаты 0..2 с клампом (полоса кладки у потолка и низ
|
||||
* стены ложатся на крайние ряды). Цепочкой сравнений, а не делением на 63:
|
||||
* у SDCC z80 одно деление ~5 400 тактов (memory sdcc_z80_division_hoisting). */
|
||||
static uint8_t cd_row_of(int y)
|
||||
{
|
||||
y -= POP_YOFF;
|
||||
if (y < 63) return 0;
|
||||
if (y < 126) return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Колонки прямоугольника [x..x1] (включительно) -> битовая маска. Тайл ровно
|
||||
* 32 px и начинается с x=0, поэтому колонка — просто сдвиг. */
|
||||
static uint16_t cd_cols_of(int x, int x1)
|
||||
{
|
||||
uint8_t c0, c1;
|
||||
if (x < 0) x = 0;
|
||||
if (x1 > 319) x1 = 319;
|
||||
if (x1 < x) return 0; /* весь прямоугольник вне экрана */
|
||||
c0 = (uint8_t)(x >> 5);
|
||||
c1 = (uint8_t)(x1 >> 5);
|
||||
return (uint16_t)(CD_LOW[c1 + 1] & ~CD_LOW[c0]);
|
||||
}
|
||||
|
||||
/* Обе страницы помечаются сразу (персонаж чинится на каждой в свой кадр),
|
||||
* поэтому цикл развёрнут: индекс-переменная заставляла SDCC считать адрес
|
||||
* каждой ячейки сложением. Зовётся с КАЖДОГО блита фона. */
|
||||
void pop_cd_touch(int x, int y, int w, int h)
|
||||
{
|
||||
int x1 = x + w, y1 = y + h;
|
||||
uint8_t d = pop_cd_dirty;
|
||||
if (!(d & 1)) {
|
||||
pop_cd_dx0[0] = x; pop_cd_dy0[0] = y;
|
||||
pop_cd_dx1[0] = x1; pop_cd_dy1[0] = y1;
|
||||
} else {
|
||||
if (x < pop_cd_dx0[0]) pop_cd_dx0[0] = x;
|
||||
if (y < pop_cd_dy0[0]) pop_cd_dy0[0] = y;
|
||||
if (x1 > pop_cd_dx1[0]) pop_cd_dx1[0] = x1;
|
||||
if (y1 > pop_cd_dy1[0]) pop_cd_dy1[0] = y1;
|
||||
}
|
||||
if (!(d & 2)) {
|
||||
pop_cd_dx0[1] = x; pop_cd_dy0[1] = y;
|
||||
pop_cd_dx1[1] = x1; pop_cd_dy1[1] = y1;
|
||||
} else {
|
||||
if (x < pop_cd_dx0[1]) pop_cd_dx0[1] = x;
|
||||
if (y < pop_cd_dy0[1]) pop_cd_dy0[1] = y;
|
||||
if (x1 > pop_cd_dx1[1]) pop_cd_dx1[1] = x1;
|
||||
if (y1 > pop_cd_dy1[1]) pop_cd_dy1[1] = y1;
|
||||
uint16_t cols = cd_cols_of(x, x + w - 1);
|
||||
uint8_t r0, r1;
|
||||
if (!cols) return;
|
||||
r0 = cd_row_of(y);
|
||||
r1 = cd_row_of(y + h - 1);
|
||||
pop_cd_dmask[0][r0] |= cols;
|
||||
pop_cd_dmask[1][r0] |= cols;
|
||||
if (r1 != r0) {
|
||||
pop_cd_dmask[0][r1] |= cols;
|
||||
pop_cd_dmask[1][r1] |= cols;
|
||||
if (r1 - r0 > 1) { /* прямоугольник накрыл все три ряда */
|
||||
pop_cd_dmask[0][1] |= cols;
|
||||
pop_cd_dmask[1][1] |= cols;
|
||||
}
|
||||
}
|
||||
pop_cd_dirty = 3;
|
||||
}
|
||||
|
||||
/* Задел ли прямоугольник (ЭКРАННЫЕ координаты, x1/y1 ВКЛЮЧИТЕЛЬНО) то, что
|
||||
* трогали на странице p. Резидент: зовёт pop_cdraw.c из банка 4 прямым
|
||||
* вызовом, без трамплина. */
|
||||
uint8_t pop_cd_hit(uint8_t p, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
uint16_t cols;
|
||||
uint8_t r0, r1;
|
||||
if (!(pop_cd_dirty & (1 << p))) return 0;
|
||||
cols = cd_cols_of(x0, x1);
|
||||
if (!cols) return 0;
|
||||
r0 = cd_row_of(y0);
|
||||
r1 = cd_row_of(y1);
|
||||
if (pop_cd_dmask[p][r0] & cols) return 1;
|
||||
if (r1 != r0) {
|
||||
if (pop_cd_dmask[p][r1] & cols) return 1;
|
||||
if (r1 - r0 > 1 && (pop_cd_dmask[p][1] & cols)) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Страница приведена в порядок — снять с неё метку целиком. */
|
||||
void pop_cd_clear(uint8_t p)
|
||||
{
|
||||
pop_cd_dmask[p][0] = pop_cd_dmask[p][1] = pop_cd_dmask[p][2] = 0;
|
||||
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << p));
|
||||
}
|
||||
|
||||
/* ---- Блит куска атласа ---------------------------------------------- */
|
||||
/* Клипованный путь — ОТДЕЛЬНОЙ функцией. У него девять 16-битных локалей,
|
||||
* и SDCC заводит под них кадр IX; пока он жил в общем теле, за этот кадр
|
||||
|
||||
@@ -555,8 +555,14 @@ void pop_process_trobs(uint8_t cur_room) __banked
|
||||
|
||||
if (room == cur_room &&
|
||||
(code == TILE_TORCH || code == TILE_TORCH_DEBRIS || code == TILE_POTION)) {
|
||||
/* Пламя/пузырёк анимируются КАЖДЫЙ кадр и в фон не запечены —
|
||||
* рисуем поверх (сами делают heal своей области). */
|
||||
/* Пламя ЗАПЕКАЕТСЯ в фон (GFX_BANK_NORMAL внутри pop_torch_draw),
|
||||
* пузырёк зелья — нет, ему нужен heal. Порядок «пламя раньше
|
||||
* перерисовок тайлов» обязателен: heal чомпера-соседа вернёт
|
||||
* запечённый огонь из ОЗУ-копии, а его собственная графика ляжет
|
||||
* ПОВЕРХ — ровно как в оригинале, где draw_tile_anim_right (пламя
|
||||
* левого соседа) идёт до draw_tile_anim (сам чомпер). Выносить
|
||||
* отрисовку ПОСЛЕ pop_redraw_needed нельзя: пламя окажется поверх
|
||||
* челюстей. */
|
||||
/* Таблицей, а не tp/10 и tp%10: у Z80 деления нет, SDCC зовёт
|
||||
* __divuchar + __moduchar — на два факела это четыре вызова за
|
||||
* кадр (замер MAME). Тайл всегда 0..29. */
|
||||
|
||||
@@ -252,6 +252,10 @@ static uint8_t room_label_redraw;
|
||||
* терялась: разбитые плиты и выпитые зелья «возвращались» при возврате в
|
||||
* комнату (BUG-LVLSTATE-1). */
|
||||
#define TILE_DEBRIS 0x0E
|
||||
#define TILE_CLOSER 0x06
|
||||
#define TILE_OPENER 0x0F
|
||||
#define TILE_TORCH 0x13
|
||||
#define TILE_TORCH_DEBRIS 0x1E
|
||||
#define TILE_EMPTY 0x00
|
||||
#define TILE_FLOOR 0x01
|
||||
|
||||
@@ -815,15 +819,36 @@ int main(void)
|
||||
gfx_set_draw_page(back);
|
||||
}
|
||||
}
|
||||
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
|
||||
if (pop_loose_fell) { /* remove_loose: сама плита исчезла */
|
||||
uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
|
||||
uint8_t fcol = (uint8_t)(fpos % 10);
|
||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||
pop_loose_fell = 0;
|
||||
pop_level_set_tile(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
|
||||
pop_level_set_tile(cur_room, fpos, TILE_EMPTY);
|
||||
}
|
||||
if (pop_loose_exit) { /* кусок долетел до низа комнаты — посадка ниже */
|
||||
uint8_t fcol = (uint8_t)(pop_loose_exit - 1);
|
||||
uint8_t from = pop_loose_exit_room;
|
||||
uint8_t down = pop_room_link(from, LINK_DOWN);
|
||||
pop_loose_exit = 0;
|
||||
if (down) {
|
||||
int8_t lrow = pop_room_col_landing(down, fcol);
|
||||
if (lrow >= 0) pop_level_set_tile(down, (uint8_t)(lrow * 10 + fcol), TILE_DEBRIS);
|
||||
if (lrow >= 0) {
|
||||
uint8_t lpos = (uint8_t)(lrow * 10 + fcol);
|
||||
uint8_t tt = pop_level_tile(down, lpos);
|
||||
/* Кусок сел в комнате СНИЗУ — кнопка там нажимается так
|
||||
* же, как в своей (loose_land, seg007:11E8): на opener
|
||||
* подставляется button_type = щебень, то есть «открыть
|
||||
* НАСОВСЕМ» (modifier 0xFF). Второе место того же
|
||||
* порта: своя комната — в pop_map.c. */
|
||||
if (tt == TILE_OPENER || tt == TILE_CLOSER)
|
||||
pop_trigger_button(down, lpos,
|
||||
(uint8_t)(tt == TILE_OPENER ? TILE_DEBRIS : tt),
|
||||
pop_trob_modif(down)[lpos]);
|
||||
/* На факеле остаётся отдельный тайл «факел с щебнем»
|
||||
* (seg007:1067), а не обычный щебень. */
|
||||
pop_level_set_tile(down, lpos,
|
||||
(uint8_t)((tt == TILE_TORCH || tt == TILE_TORCH_DEBRIS)
|
||||
? TILE_TORCH_DEBRIS : TILE_DEBRIS));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pop_ceil_fell) { /* провалилась плита-ПОТОЛОК (ряд 2 комнаты сверху) */
|
||||
@@ -905,7 +930,7 @@ int main(void)
|
||||
* для неё снят. ПОСЛЕ отрисовки, а не до: слой фона этого же кадра
|
||||
* взводит бит и обязан быть учтён. Всё, что рисуется ниже
|
||||
* (mob поверх Кида, борта), взводит его заново само. */
|
||||
pop_cd_dirty = (uint8_t)(pop_cd_dirty & ~(1 << back));
|
||||
pop_cd_clear(back);
|
||||
pop_room_clip_borders(); /* почистить борта — только в кадрах падения (гейт) */
|
||||
|
||||
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
||||
|
||||
Reference in New Issue
Block a user