MEM-BANK2 шаг 3: холодная половина слоя фона в банк 7 (90.4% -> 35.8%)
pop_bg.c разрезан по ЧАСТОТЕ вызова, а не по размеру:
pop_bg.c (банк 2) горячее fore-проход, оверлеи, кладка, клип
pop_room.c (банк 7) холодное draw_tile, точечные перерисовки, mob,
загрузка атласов
Стык — три тонкие __banked-обёртки (wall_pattern, wall_pattern_reset,
draw_gate_back): тела остаются непомеченными, поэтому горячий fore-проход,
зовущий wall_pattern до девяти раз за кадр, платит ноль, а трамплин
достаётся только холодному пути — ~40 вызовов на вход в комнату, 26 000
тактов = 0.06 кадра РАЗОВО. Общее состояние (pop_loose_modif, pop_ceil_modif,
obj_row/obj_col) писучее, лежит в _DATA/W2 и видно обеим половинам.
Заодно удалена мёртвая potion_bubble (169 Б).
Грабля: n_banks объявляет само приложение (roomtest.c), а не sprinter-cc.
Восьмой банк без правки константы линкуется молча, _bank_pages[7] остаётся
0xFF, и программа встаёт намертво до первого кадра. Диагноз снят дампом
_bank_pages из MAME.
Замер (ALLOCS=3000): BANK2 11 942 -> 5 872, BANK7 6 035; _CODE и куча не
тронуты (22 556 / 4 301).
Проверено: tests-host зелёные; построчная сверка pop_bg.c+pop_room.c против
дорефакторного pop_bg.c — ни одной строки логики не пропало; 8 комнат в MAME
до/после совпали попиксельно по активному экрану (различия только в фазе
анимации факелов и кадре Кида); живой прогон с переходами комнат, боем и
воротами сверен контрольным запуском HEAD-бинаря.
DRAW-COST поднят в приоритете: замер пользователя (ур.1 комната 3, страж
убит, Кид стоит) — синяя 80%, зелёная 20%, циан 110%, итого ~210 %
кадрового периода В ПОКОЕ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,23 @@
|
|||||||
# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам
|
# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам
|
||||||
|
|
||||||
> **Замер 2026-08-08 (актуальная сборка, ALLOCS=3000).** `_CODE` 20 524 Б,
|
> **Замер 2026-08-08, после MEM-BANK2 (актуальная сборка, ALLOCS=3000).**
|
||||||
> данные 4 375, куча 6 333. Банки: 1 (`guards.c`) 2 519, 2 (`pop_bg.c`)
|
> `_CODE` 22 556 Б, данные 4 375, куча 4 301. Банки: 1 (`guards.c`) 2 289,
|
||||||
> 15 045, 3 (`pop_map.c`) 8 713, 4 (`pop_cdraw.c`) 5 905, 5 (`pop_ctrl.c`)
|
> 2 (`pop_bg.c`, ГОРЯЧАЯ половина фона) 5 872, 3 (`pop_map.c`) 8 483,
|
||||||
> 2 270, 6 (`pop_trob.c`) 3 119 — все из 16 384. Резидент похудел на 4.3 КБ
|
> 4 (`pop_cdraw.c`) 5 664, 5 (`pop_ctrl.c`) 2 040, 6 (`pop_trob.c`) 2 889,
|
||||||
> после DRAW-CHAR (отрисовка персонажей уехала из W1 в банк 4 и слилась в
|
> 7 (`pop_room.c`, ХОЛОДНАЯ половина фона) 6 035 — все из 16 384.
|
||||||
> одну на всех Char); банк 2 при этом почти не разгрузился — свободно
|
>
|
||||||
> 1 339 Б.
|
> Слой фона разрезан надвое по ЧАСТОТЕ ВЫЗОВА (`TASKS_CLOSED.md#mem-bank2`):
|
||||||
|
> общие листья — в резидент `pop_tile.c` (W1 замаплен всегда, таблицы видны
|
||||||
|
> обеим половинам), горячий fore-проход остался в банке 2, холодная отрисовка
|
||||||
|
> комнаты и точечные перерисовки уехали в банк 7. Стык — три тонкие
|
||||||
|
> `__banked`-обёртки, чтобы трамплин платил только холодный путь.
|
||||||
|
> **Банк 2: 90.4 % -> 35.8 %.** Резидент вырос на 2 КБ (листья) — куча
|
||||||
|
> 6 333 -> 4 301 Б, это плата за то, что таблицы должны быть видны из двух
|
||||||
|
> банков.
|
||||||
|
>
|
||||||
|
> Число банков дублируется: `--bank N=` в Makefile И `const uint8_t n_banks`
|
||||||
|
> в `roomtest.c`. Расхождение — не ошибка сборки, а зависание до первого
|
||||||
|
> кадра.
|
||||||
>
|
>
|
||||||
> **Замер 2026-08-01 (историческая отметка).** `_CODE` 25 119 Б, `_DATA` 3 709,
|
> **Замер 2026-08-01 (историческая отметка).** `_CODE` 25 119 Б, `_DATA` 3 709,
|
||||||
> куча ~2.4 КБ. Банки: 1 (`guards.c`) 1 896, 2 (`pop_bg.c`) 13 792,
|
> куча ~2.4 КБ. Банки: 1 (`guards.c`) 1 896, 2 (`pop_bg.c`) 13 792,
|
||||||
@@ -17,10 +28,10 @@
|
|||||||
> вынесена из банка 2 в собственный банк 4, потому что банк 2 подошёл к
|
> вынесена из банка 2 в собственный банк 4, потому что банк 2 подошёл к
|
||||||
> потолку (16 021 из 16 384) — коммит `2f3e854`.
|
> потолку (16 021 из 16 384) — коммит `2f3e854`.
|
||||||
>
|
>
|
||||||
> **Свободного места в банке 2 осталось ~2.6 КБ**, а туда же просятся
|
> **Свободного места в банке 2 теперь 10.5 КБ**, в банке 7 — 10.3 КБ; туда
|
||||||
> чомперы, зеркало и второй тайлсет (palace). Прежде чем начинать
|
> просятся чомперы, зеркало и второй тайлсет (palace). Прежде чем начинать
|
||||||
> `levels_plan.md` §3 — посчитать, куда это ляжет. Свободные номера банков
|
> `levels_plan.md` §3 — всё равно посчитать, куда это ляжет. Следующий
|
||||||
> есть, гранулярность — файл.
|
> свободный номер банка — 8, гранулярность — файл.
|
||||||
>
|
>
|
||||||
> Из плана ниже **не сделаны шаги 5 (данные: `room_modif`, `dl1/dl2`,
|
> Из плана ниже **не сделаны шаги 5 (данные: `room_modif`, `dl1/dl2`,
|
||||||
> `_kbdraw_down`; потенциал ~1.5 КБ) и 7 (дедуп `draw_tile`, отложен по
|
> `_kbdraw_down`; потенциал ~1.5 КБ) и 7 (дедуп `draw_tile`, отложен по
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ Kid — `kid_data.h` (генерится `pop_extract_kid_data.py`).
|
|||||||
|------|--------------------|
|
|------|--------------------|
|
||||||
| `roomtest.c` | Главный цикл: дабл-буфер (2 страницы + флип на vsync), tick→draw порядок слоёв. |
|
| `roomtest.c` | Главный цикл: дабл-буфер (2 страницы + флип на vsync), tick→draw порядок слоёв. |
|
||||||
| `pop_tile.c/.h` | Общие ЛИСТЬЯ слоя фона — РЕЗИДЕНТ W1: блит куска атласа, чтение тайла комнаты, `tile_table`. Резидент потому, что их зовут обе половины слоя фона из разных банков, а `const`-таблицы банка из чужого банка не видны. |
|
| `pop_tile.c/.h` | Общие ЛИСТЬЯ слоя фона — РЕЗИДЕНТ W1: блит куска атласа, чтение тайла комнаты, `tile_table`. Резидент потому, что их зовут обе половины слоя фона из разных банков, а `const`-таблицы банка из чужого банка не видны. |
|
||||||
| `pop_bg.c/.h` | Отрисовка фона композицией тайлов (`seg008` draw_tile/wall_pattern), fore-окклюзия поверх ЛЮБОГО персонажа (`seg003` redraw_at_char/char2 — один проход на всех Char), loose-полы (shake/bake/mob, `seg007/008`). |
|
| `pop_bg.c/.h` | ГОРЯЧАЯ половина слоя фона (банк 2, каждый кадр): fore-окклюзия поверх ЛЮБОГО персонажа (`seg003` redraw_at_char/char2 — один проход на всех Char), оверлеи кромки, кладка стены (`wall_pattern`), клип. `pop_bg.h` — публичный API ВСЕГО слоя (обеих половин). |
|
||||||
|
| `pop_room.c` | ХОЛОДНАЯ половина слоя фона (банк 7, раз на комнату/событие): полная отрисовка комнаты (`seg008` draw_tile), точечные перерисовки тайлов (пики, ворота, кнопки, дверь уровня, дрожащие плиты), падающие куски loose (`seg007` mob), загрузка атласов. |
|
||||||
|
| `_pop_bg.h` | Внутренний контракт между половинами: общее состояние + три тонкие `__banked`-обёртки. Там же расписано, почему рез прошёл именно здесь и сколько стоит. |
|
||||||
| `pop_kid.c/.h` | Анимация/движение: интерпретатор seqtbl `play_seq` + frame_table (`seg006`), окна `Char` (loadkid/loadshad), атласы Кида. |
|
| `pop_kid.c/.h` | Анимация/движение: интерпретатор seqtbl `play_seq` + frame_table (`seg006`), окна `Char` (loadkid/loadshad), атласы Кида. |
|
||||||
| `pop_cdraw.c/.h` | ОТРИСОВКА персонажей — одна на всех Char (порт `add_kid_to_objtable`/`add_guard_to_objtable`, `seg008:22F0/2324`): спрайт, clip_char, брызги, клинок, heal; + полоса HP и палитры соперника. |
|
| `pop_cdraw.c/.h` | ОТРИСОВКА персонажей — одна на всех Char (порт `add_kid_to_objtable`/`add_guard_to_objtable`, `seg008:22F0/2324`): спрайт, clip_char, брызги, клинок, heal; + полоса HP и палитры соперника. |
|
||||||
| `pop_ctrl.c/.h` | Управление: диспетчер `control()` (`seg005`) + ввод `read_user_control` (`seg006`) поверх `<kbd_raw.h>`. |
|
| `pop_ctrl.c/.h` | Управление: диспетчер `control()` (`seg005`) + ввод `read_user_control` (`seg006`) поверх `<kbd_raw.h>`. |
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
EXTRA_FLAGS ?= --gfx 256 --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
@@ -105,4 +105,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
|||||||
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
||||||
cd $(TC) && python3 pop_extract_kid_data.py
|
cd $(TC) && python3 pop_extract_kid_data.py
|
||||||
|
|
||||||
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.c _pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
||||||
|
|||||||
@@ -322,6 +322,123 @@ BANK4 pop_cdraw 4 625 5 905 +1 280 (сюда переехала отрис
|
|||||||
|
|
||||||
|
|
||||||
<a id="clip-1"></a>
|
<a id="clip-1"></a>
|
||||||
|
### <a id="mem-bank2"></a>MEM-BANK2. Разгрузка банка 2 — **ЗАКРЫТА 2026-08-08**
|
||||||
|
|
||||||
|
Банк 2 (`pop_bg.c`) упирался в потолок: 90.8 % ещё до чомперов. Разбор по
|
||||||
|
символам показал, что он держит ДВЕ разные вещи — горячий fore-проход (каждый
|
||||||
|
кадр) и холодную отрисовку тайлов (вход в комнату, точечные перерисовки), —
|
||||||
|
а общие «листья» (`blit_b`, `tile_code`, `tile_table`, `wall_pattern`) нужны
|
||||||
|
обеим.
|
||||||
|
|
||||||
|
**Ключевое ограничение платформы** (записано в memory
|
||||||
|
`sdcc_banked_call_rules`): писучие данные банка лежат в `_DATA`/W2 и видны
|
||||||
|
всем, а `const`-таблицы — в СТРАНИЦЕ банка, и из другого банка их не
|
||||||
|
прочитать вовсе. Плюс трамплин выбирается ОБЪЯВЛЕНИЕМ: пометить лист
|
||||||
|
`__banked` — значит заставить платить и горячих вызывающих (654 такта на
|
||||||
|
круг).
|
||||||
|
|
||||||
|
**Шаг 1 — общие листья в РЕЗИДЕНТ** (`pop_tile.c` + внутренний `pop_tile.h`).
|
||||||
|
W1 замаплен всегда, поэтому и горячая половина, и будущая холодная зовут их
|
||||||
|
прямым `call` и читают таблицы напрямую — без дублей и без трамплинов.
|
||||||
|
Уехали: `blit_b`, `env_b/wall_b/fore_b/pot_b`, `tile_code/tile_mod/
|
||||||
|
tile_code_drawn/wall_modifier`, `heal_off`, `get_loose_frame`,
|
||||||
|
`potion_flask`, `pop_fore_set_clip`, `pop_room_set_below/above` + таблицы
|
||||||
|
`tile_table`, `COL_XH`, `WALL_FRAM_*`, `SPIKES_FRAM_LEFT`,
|
||||||
|
`LOOSE_FRAM_LEFT/BOTTOM`. Заодно три функции перестали быть `__banked` —
|
||||||
|
минус трамплин на кадр.
|
||||||
|
|
||||||
|
**Шаг 2 — дедуп внутри банка.** `wall_pattern` (808 → 394) и `wall_rnd`
|
||||||
|
(786 → 654): четыре ветки по виду стены (SWS/SWW/WWS/WWW) отличались ТОЛЬКО
|
||||||
|
набором кусков и числами в одной и той же серии `prandom` — сведены к
|
||||||
|
таблицам `WP_PARTS` и `WR_RULE`. Порядок вызовов `prandom` сохранён
|
||||||
|
дословно (иначе разъедется раскладка кладки).
|
||||||
|
|
||||||
|
**Грабля, пойманная замером:** после выноса `tile_table` из `static const` в
|
||||||
|
глобальную SDCC перестала сворачивать `tile_table[10].fore_x/fore_y` в
|
||||||
|
константы и стала читать их в рантайме — `potion_flask` раздулся 115 → 265 Б.
|
||||||
|
Лечится макросами, которыми инициализируется та же запись таблицы (одно место
|
||||||
|
правки, дублирования данных нет): 265 → 230.
|
||||||
|
|
||||||
|
**Замер (ALLOCS=3000):**
|
||||||
|
|
||||||
|
```
|
||||||
|
было после ш.1 после ш.2
|
||||||
|
BANK2 14 815 12 460 11 942 (90.4 % -> 72.9 %, свободно 4442 Б)
|
||||||
|
_CODE 20 064 22 591 22 556 (куча 6793 -> 4301 Б)
|
||||||
|
```
|
||||||
|
|
||||||
|
С релизным `ALLOCS=100000` банк 2 будет ещё примерно на 500-600 Б меньше
|
||||||
|
(замер до разгрузки: 14 815 -> 14 176).
|
||||||
|
|
||||||
|
**Проверено:** `tests-host` зелёные; в MAME комната 1 уровня 1 после дедупа
|
||||||
|
совпала с дорефакторным снимком **попиксельно (0 различий из 227 520)**,
|
||||||
|
комната 3 (сплошная кладка, все четыре вида стены и марки) — та же раскладка
|
||||||
|
кирпича.
|
||||||
|
|
||||||
|
**Шаг 3 — ХОЛОДНАЯ половина в банк 7** (`pop_room.c` + внутренний
|
||||||
|
`_pop_bg.h`). Граница проведена по частоте вызова, а не по размеру:
|
||||||
|
|
||||||
|
```
|
||||||
|
pop_bg.c (банк 2) ГОРЯЧЕЕ fore-проход, оверлеи, кладка, клип каждый кадр
|
||||||
|
pop_room.c (банк 7) ХОЛОДНОЕ draw_tile, точечные перерисовки, mob, раз на комнату
|
||||||
|
загрузка атласов / на событие
|
||||||
|
```
|
||||||
|
|
||||||
|
Обратных зависимостей нет вовсе: холодная половина зовёт горячую ровно в
|
||||||
|
трёх местах (`wall_pattern`, `wall_pattern_reset`, `draw_gate_back`) — и
|
||||||
|
через ТОНКИЕ `__banked`-обёртки, а не пометкой самих тел. Тела остаются
|
||||||
|
непомеченными, поэтому fore-проход, который зовёт `wall_pattern` до девяти
|
||||||
|
раз за кадр, платит ноль. Общее состояние (`pop_loose_modif`,
|
||||||
|
`pop_ceil_modif`, `obj_row/obj_col`) — писучее, лежит в `_DATA`/W2 и видно
|
||||||
|
обеим половинам; `obj_row/obj_col` для этого перестали быть `static`.
|
||||||
|
|
||||||
|
**Цена трамплинов:** полная отрисовка комнаты — ~40 вызовов через границу
|
||||||
|
(по два `wall_pattern` на стенной тайл), 26 000 тактов = 0.06 кадра РАЗОВО
|
||||||
|
на вход в комнату. Анимация пик/ворот — 2-4 вызова на кадр, ~1 %. Ровно
|
||||||
|
тот порядок, который был признан приемлемым при постановке задачи.
|
||||||
|
|
||||||
|
Заодно удалена мёртвая `potion_bubble` (169 Б): её работу давно делают
|
||||||
|
`pop_potion_flask` (резидент) и `pop_potion_draw`, вызывающих не осталось.
|
||||||
|
|
||||||
|
**ГРАБЛЯ, стоившая прогона: `n_banks` объявляет САМО ПРИЛОЖЕНИЕ**
|
||||||
|
(`roomtest.c`), а не `sprinter-cc`. Восьмой банк без правки этой константы
|
||||||
|
собирается и линкуется без единого предупреждения, `_bank_pages[7]`
|
||||||
|
остаётся 0xFF, и трамплин уводит в мусор: exe грузится, но программа встаёт
|
||||||
|
намертво ещё до первого кадра — на экране остаётся приглашение DSS.
|
||||||
|
Диагноз снят дампом `_bank_pages` из MAME (`FF EC ED EE EF F0 F1 FF` —
|
||||||
|
семь страниц вместо восьми), а не гаданием. Предупреждение об этом в
|
||||||
|
шапке `roomtest.c` было — и всё равно не сработало, потому что число банков
|
||||||
|
живёт в двух местах.
|
||||||
|
|
||||||
|
**Замер (ALLOCS=3000):**
|
||||||
|
|
||||||
|
```
|
||||||
|
было ш.1 ш.2 ш.3
|
||||||
|
BANK2 14 815 12 460 11 942 5 872 (90.4 % -> 35.8 %)
|
||||||
|
BANK7 — — — 6 035 (36.8 %)
|
||||||
|
_CODE 20 064 22 591 22 556 22 556 (куча 4301 Б — не тронута)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Проверено (шаг 3):**
|
||||||
|
- `tests-host` зелёные;
|
||||||
|
- построчная сверка `pop_bg.c` + `pop_room.c` против дорефакторного
|
||||||
|
`pop_bg.c`: НИ ОДНОЙ строки логики не пропало и не изменилось —
|
||||||
|
расхождения только шапки, включения, три обёртки и удалённый мёртвый код;
|
||||||
|
- в MAME **8 комнат** (стартовая + 7 по `ROOMNAV`) сняты до и после и сверены
|
||||||
|
попиксельно по активному экрану 320×256: различия ТОЛЬКО в фазе анимации
|
||||||
|
(пламя факелов, кадр Кида), статическая кладка совпадает пиксель в пиксель;
|
||||||
|
- живой прогон: переход между комнатами, бой со стражем, решётка ворот,
|
||||||
|
окклюзия Кида ближней колонной — как на дорефакторном билде (сверено
|
||||||
|
контрольным запуском HEAD-бинаря с тем же вводом).
|
||||||
|
|
||||||
|
**Что осталось в запасе, если места снова не хватит** (не делаем, пока нет
|
||||||
|
причины): вынести из РЕЗИДЕНТА холодные части — инициализацию в `main` +
|
||||||
|
`enter_room_side`/`pop_start_level` (~1.7 КБ), загрузчик атласов Кида
|
||||||
|
(~1.2 КБ), `pop_room_load`/`pop_level_load` (~1.4 КБ). Итого ~4-4.5 КБ
|
||||||
|
резидента, то есть куча 4301 -> ~8.6 КБ. Целиком `pop_level.c` в банк
|
||||||
|
нельзя: его `pop_level_tile` зовётся из банка на КАЖДЫЙ тайл — только
|
||||||
|
расщепление модуля.
|
||||||
|
|
||||||
### CLIP-1. Аудит блитов: где клип не нужен — **СДЕЛАНО 2026-08-01**
|
### CLIP-1. Аудит блитов: где клип не нужен — **СДЕЛАНО 2026-08-01**
|
||||||
|
|
||||||
> **Итог.** Heal Кида и стража переведены на выбор ядра по тому же тесту,
|
> **Итог.** Heal Кида и стража переведены на выбор ядра по тому же тесту,
|
||||||
|
|||||||
@@ -17,13 +17,14 @@
|
|||||||
Уровень 3: **скелет сделан и проверен в MAME 2026-08-07** (бой, падение в
|
Уровень 3: **скелет сделан и проверен в MAME 2026-08-07** (бой, падение в
|
||||||
пропасть, окклюзия), чомперов ещё нет.
|
пропасть, окклюзия), чомперов ещё нет.
|
||||||
|
|
||||||
**Разгрузка банка 2 сделана 2026-08-08** ([MEM-BANK2](#mem-bank2)): 90.4 % →
|
**Разгрузка банка 2 сделана 2026-08-08** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2)): 90.4 % →
|
||||||
**72.9 %, свободно 4442 Б** — чомперам места хватает.
|
**35.8 %, свободно 10 512 Б** — холодная половина уехала в банк 7
|
||||||
|
(`pop_room.c`), чомперам места с запасом.
|
||||||
|
|
||||||
**DRAW-CHAR сделана и ПРОВЕРЕНА 2026-08-08** (протокол и замеры —
|
**DRAW-CHAR сделана и ПРОВЕРЕНА 2026-08-08** (протокол и замеры —
|
||||||
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char)): отрисовка теперь одна на
|
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char)): отрисовка теперь одна на
|
||||||
всех `Char`. Банку 2 она дала всего +265 Б — место под чомперов дала уже
|
всех `Char`. Банку 2 она дала всего +265 Б — место под чомперов дала уже
|
||||||
[MEM-BANK2](#mem-bank2).
|
[MEM-BANK2](TASKS_CLOSED.md#mem-bank2).
|
||||||
|
|
||||||
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
|
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
|
||||||
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
|
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
|
||||||
@@ -50,10 +51,11 @@
|
|||||||
|---|--------|-----|-----------|
|
|---|--------|-----|-----------|
|
||||||
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME |
|
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME |
|
||||||
| — | [DRAW-CHAR](TASKS_CLOSED.md#draw-char) | отрисовка одна на всех `Char` — **закрыта и проверена 2026-08-08** | — |
|
| — | [DRAW-CHAR](TASKS_CLOSED.md#draw-char) | отрисовка одна на всех `Char` — **закрыта и проверена 2026-08-08** | — |
|
||||||
| — | [MEM-BANK2](#mem-bank2) | разгрузка банка 2 — **сделана 2026-08-08**: 90.4 % → 72.9 %, свободно 4442 Б | — |
|
| — | [MEM-BANK2](TASKS_CLOSED.md#mem-bank2) | разгрузка банка 2 — **закрыта 2026-08-08**: 90.4 % → 35.8 %, свободно 10 512 Б | — |
|
||||||
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
|
||||||
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
|
||||||
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
|
||||||
|
| — | [DRAW-COST](#draw-cost) | **кадр не укладывается в бюджет: ~210 % кадрового периода в ПОКОЕ** (замер 2026-08-08) | плавность на ВСЕХ уровнях |
|
||||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
||||||
|
|
||||||
@@ -184,66 +186,6 @@
|
|||||||
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
|
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
|
||||||
живой страж — переход не происходит.
|
живой страж — переход не происходит.
|
||||||
|
|
||||||
### <a id="mem-bank2"></a>MEM-BANK2. Разгрузка банка 2 — **шаги 1-2 сделаны 2026-08-08**
|
|
||||||
|
|
||||||
Банк 2 (`pop_bg.c`) упирался в потолок: 90.8 % ещё до чомперов. Разбор по
|
|
||||||
символам показал, что он держит ДВЕ разные вещи — горячий fore-проход (каждый
|
|
||||||
кадр) и холодную отрисовку тайлов (вход в комнату, точечные перерисовки), —
|
|
||||||
а общие «листья» (`blit_b`, `tile_code`, `tile_table`, `wall_pattern`) нужны
|
|
||||||
обеим.
|
|
||||||
|
|
||||||
**Ключевое ограничение платформы** (записано в memory
|
|
||||||
`sdcc_banked_call_rules`): писучие данные банка лежат в `_DATA`/W2 и видны
|
|
||||||
всем, а `const`-таблицы — в СТРАНИЦЕ банка, и из другого банка их не
|
|
||||||
прочитать вовсе. Плюс трамплин выбирается ОБЪЯВЛЕНИЕМ: пометить лист
|
|
||||||
`__banked` — значит заставить платить и горячих вызывающих (654 такта на
|
|
||||||
круг).
|
|
||||||
|
|
||||||
**Шаг 1 — общие листья в РЕЗИДЕНТ** (`pop_tile.c` + внутренний `pop_tile.h`).
|
|
||||||
W1 замаплен всегда, поэтому и горячая половина, и будущая холодная зовут их
|
|
||||||
прямым `call` и читают таблицы напрямую — без дублей и без трамплинов.
|
|
||||||
Уехали: `blit_b`, `env_b/wall_b/fore_b/pot_b`, `tile_code/tile_mod/
|
|
||||||
tile_code_drawn/wall_modifier`, `heal_off`, `get_loose_frame`,
|
|
||||||
`potion_flask`, `pop_fore_set_clip`, `pop_room_set_below/above` + таблицы
|
|
||||||
`tile_table`, `COL_XH`, `WALL_FRAM_*`, `SPIKES_FRAM_LEFT`,
|
|
||||||
`LOOSE_FRAM_LEFT/BOTTOM`. Заодно три функции перестали быть `__banked` —
|
|
||||||
минус трамплин на кадр.
|
|
||||||
|
|
||||||
**Шаг 2 — дедуп внутри банка.** `wall_pattern` (808 → 394) и `wall_rnd`
|
|
||||||
(786 → 654): четыре ветки по виду стены (SWS/SWW/WWS/WWW) отличались ТОЛЬКО
|
|
||||||
набором кусков и числами в одной и той же серии `prandom` — сведены к
|
|
||||||
таблицам `WP_PARTS` и `WR_RULE`. Порядок вызовов `prandom` сохранён
|
|
||||||
дословно (иначе разъедется раскладка кладки).
|
|
||||||
|
|
||||||
**Грабля, пойманная замером:** после выноса `tile_table` из `static const` в
|
|
||||||
глобальную SDCC перестала сворачивать `tile_table[10].fore_x/fore_y` в
|
|
||||||
константы и стала читать их в рантайме — `potion_flask` раздулся 115 → 265 Б.
|
|
||||||
Лечится макросами, которыми инициализируется та же запись таблицы (одно место
|
|
||||||
правки, дублирования данных нет): 265 → 230.
|
|
||||||
|
|
||||||
**Замер (ALLOCS=3000):**
|
|
||||||
|
|
||||||
```
|
|
||||||
было после ш.1 после ш.2
|
|
||||||
BANK2 14 815 12 460 11 942 (90.4 % -> 72.9 %, свободно 4442 Б)
|
|
||||||
_CODE 20 064 22 591 22 556 (куча 6793 -> 4301 Б)
|
|
||||||
```
|
|
||||||
|
|
||||||
С релизным `ALLOCS=100000` банк 2 будет ещё примерно на 500-600 Б меньше
|
|
||||||
(замер до разгрузки: 14 815 -> 14 176).
|
|
||||||
|
|
||||||
**Проверено:** `tests-host` зелёные; в MAME комната 1 уровня 1 после дедупа
|
|
||||||
совпала с дорефакторным снимком **попиксельно (0 различий из 227 520)**,
|
|
||||||
комната 3 (сплошная кладка, все четыре вида стены и марки) — та же раскладка
|
|
||||||
кирпича.
|
|
||||||
|
|
||||||
**Что осталось в запасе, если места снова не хватит** (не делаем, пока нет
|
|
||||||
причины): вынести ХОЛОДНУЮ половину (`draw_tile` 1800 + точечные
|
|
||||||
перерисовки 1438 + падающие плиты 1898 ≈ 5.1 КБ) в свободный банк 7. Теперь
|
|
||||||
это дёшево: листья уже в резиденте, а `wall_pattern`/`wall_rnd`/марки/
|
|
||||||
`draw_gate_back` нужны обеим половинам — им понадобится тонкая `__banked`-
|
|
||||||
обёртка (тело остаётся непомеченным, горячие вызывающие платят ноль).
|
|
||||||
|
|
||||||
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
|
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
|
||||||
|
|
||||||
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
|
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
|
||||||
@@ -254,10 +196,12 @@ _CODE 20 064 22 591 22 556 (куча 6793 -> 4301 Б)
|
|||||||
коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка
|
коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка
|
||||||
кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`).
|
кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`).
|
||||||
|
|
||||||
**Место в банке 2 расчищено** ([MEM-BANK2](#mem-bank2), 2026-08-08): 72.9 %,
|
**Место в банке 2 расчищено** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2), 2026-08-08):
|
||||||
**свободно 4442 Б** (было 1074 Б). Считать место ДО кодинга всё равно
|
35.8 %, **свободно 10 512 Б** (было 1074 Б) — холодная половина слоя фона
|
||||||
обязательно (`levels_plan.md` §5.1), и если не хватит — в запасе вынос
|
уехала в банк 7 (`pop_room.c`). Считать место ДО кодинга всё равно
|
||||||
холодной половины `pop_bg` в банк 7, там расписано как.
|
обязательно (`levels_plan.md` §5.1); чомпер — это анимация тайла (холодная
|
||||||
|
перерисовка), поэтому его отрисовка ложится в `pop_room.c`, а состояние —
|
||||||
|
в `room_modif`, как у пик и ворот.
|
||||||
|
|
||||||
**Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь
|
**Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь
|
||||||
набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS`
|
набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS`
|
||||||
@@ -372,10 +316,32 @@ offset 22790, **240 байт** = 5 палитр × 16 цветов × 3 байт
|
|||||||
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
|
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
|
||||||
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
|
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)).
|
||||||
|
|
||||||
### <a id="draw-cost"></a>DRAW-COST. Стоимость fore-прохода после DRAW-CHAR
|
### <a id="draw-cost"></a>DRAW-COST. Кадр НЕ УКЛАДЫВАЕТСЯ в бюджет — нужна оптимизация
|
||||||
|
|
||||||
**Наблюдение пользователя на приёмке 2026-08-08:** циан-полоса профиля
|
> **ЗАМЕР ПОЛЬЗОВАТЕЛЯ 2026-08-08 (после MEM-BANK2).** Уровень 1, комната 3,
|
||||||
(спрайты + fore) выросла — «в пределах, но оптимизация отдельной задачей».
|
> страж УБИТ, Кид СТОИТ (то есть это НЕ худший случай — ни боя, ни движения):
|
||||||
|
>
|
||||||
|
> | полоса | фаза | доля кадрового периода |
|
||||||
|
> |---|---|---|
|
||||||
|
> | синяя (`PROF(2)`) | ввод + heal + логика | ~80 % |
|
||||||
|
> | зелёная (`PROF(4)`) | слой фона (loose/ловушки) | ~20 % |
|
||||||
|
> | циан (`PROF(6)`) | спрайты + fore-проход | **~110 %** |
|
||||||
|
>
|
||||||
|
> **Итого ~210 % — больше ДВУХ кадровых периодов на кадр.** Это много; в
|
||||||
|
> покое должно оставаться место под бой, движение и вторую фигуру.
|
||||||
|
> Приоритет задачи поднят: это уже не косметика профиля.
|
||||||
|
|
||||||
|
Мерить в ЭТОЙ точке (комната 3, страж убит, Кид стоит) — она воспроизводима
|
||||||
|
и даёт нижнюю границу; худший случай (бой + бег + падающая плита) считать
|
||||||
|
отдельно. Метод — memory `z80_profiling_method` (брейкпоинты с
|
||||||
|
`totalcycles`), полосы бордюра дают только раскладку по фазам.
|
||||||
|
|
||||||
|
Обратить внимание: **синие 80 % при стоящем Киде** — это подозрительно
|
||||||
|
много для «ввод + heal + логика», heal там наверняка чистит больше, чем
|
||||||
|
нужно. Разобрать синюю фазу отдельно от циана.
|
||||||
|
|
||||||
|
**Наблюдение пользователя на приёмке DRAW-CHAR 2026-08-08:** циан-полоса
|
||||||
|
профиля (спрайты + fore) выросла — тогда «в пределах».
|
||||||
|
|
||||||
**Отчего именно.** Раньше перебор тайлов у Кида шёл строго по футпринту
|
**Отчего именно.** Раньше перебор тайлов у Кида шёл строго по футпринту
|
||||||
кадра (`char_x_left/right`, seg006:1021), а он УЖЕ спрайта; расширение
|
кадра (`char_x_left/right`, seg006:1021), а он УЖЕ спрайта; расширение
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* _pop_bg.h — ВНУТРЕННИЙ контракт между двумя половинами слоя фона.
|
||||||
|
* Публичный API слоя — pop_bg.h; общие листья (резидент) — pop_tile.h.
|
||||||
|
*
|
||||||
|
* Слой фона разрезан на два банка:
|
||||||
|
* pop_bg.c (банк 2) — ГОРЯЧАЯ половина: fore-проход поверх персонажей,
|
||||||
|
* оверлеи, клип. Работает каждый кадр.
|
||||||
|
* pop_room.c (банк 7) — ХОЛОДНАЯ: полная отрисовка комнаты (draw_tile),
|
||||||
|
* точечные перерисовки тайлов, падающие плиты,
|
||||||
|
* загрузка атласов. Раз на комнату/событие.
|
||||||
|
*
|
||||||
|
* Резать пришлось потому, что банк 2 упирался в 16 КБ страницы (90.4 % до
|
||||||
|
* разгрузки), а чомперам и остальным ловушкам уровня 3 место нужно. Граница
|
||||||
|
* проведена по частоте вызова: холодная половина платит трамплин (~654 такта)
|
||||||
|
* за каждый вызов сюда, и на входе в комнату таких вызовов ~40 — 26 000
|
||||||
|
* тактов, шестая часть кадрового бюджета РАЗОВО. Для горячего пути цена
|
||||||
|
* нулевая: он зовёт свои функции прямо, а общие листья — из резидента.
|
||||||
|
*
|
||||||
|
* Что осталось в банке 2 и вынесено сюда обёртками: кладка стены
|
||||||
|
* (wall_pattern — её зовёт и fore-проход каждый кадр) и решётка ворот
|
||||||
|
* соседней комнаты (draw_gate_back — её зовёт шовная окклюзия). Обратных
|
||||||
|
* зависимостей (горячая половина → холодная) нет вовсе.
|
||||||
|
*/
|
||||||
|
#ifndef _POP_BG_H
|
||||||
|
#define _POP_BG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Состояние дрожащих плит — живёт в pop_map, читают обе половины. */
|
||||||
|
extern uint8_t pop_loose_modif[30];
|
||||||
|
extern uint8_t pop_ceil_modif[10]; /* плита-потолок: loose ряда 2 комнаты сверху */
|
||||||
|
|
||||||
|
/* Видимая высота полосы кладки у потолка (room-space y 0..7): её печёт
|
||||||
|
* холодная половина, а поверх персонажа кладёт горячая. */
|
||||||
|
#define CEIL_BAND_H 8
|
||||||
|
|
||||||
|
/* Тайл ОБЪЕКТА активного персонажа (set_objtile_at_char, seg006:1833) — в
|
||||||
|
* момент обработки этого тайла спрайт попадает в midtable. Задаёт горячая
|
||||||
|
* половина (pop_fore_over_char), читают other_overlay_tile (там же) и
|
||||||
|
* порядок «плита vs Kid» в mob_tick_one (холодная). Писучие данные банка
|
||||||
|
* лежат в _DATA/W2 и видны из любого банка — см. sdcc_banked_call_rules. */
|
||||||
|
extern int8_t pop_bg_obj_col, pop_bg_obj_row;
|
||||||
|
|
||||||
|
/* ---- Точки входа ГОРЯЧЕЙ половины для холодной (через трамплин) ------- */
|
||||||
|
void pop_wall_pattern_b(int row, int col, int which_part) __banked;
|
||||||
|
void pop_wall_pattern_reset_b(void) __banked;
|
||||||
|
void pop_gate_back_b(uint8_t modl, int xh, int dby, int dmy) __banked;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* pop_bg.c — статический фон комнаты PoP композицией слоёв (порт
|
* pop_bg.c — ГОРЯЧАЯ половина слоя фона PoP: то, что работает КАЖДЫЙ кадр —
|
||||||
* applications/PoP/toolchain/render_room.py -> seg008.c draw_tile/
|
* передний слой поверх персонажей (порт seg003 redraw_at_char + seg008
|
||||||
* wall_pattern). Подробности алгоритма — в render_room.py и
|
* set_char_collision), оверлеи кромки, кладка стены и клип. Полная
|
||||||
* memory/pop_background_strategy.
|
* отрисовка комнаты и точечные перерисовки уехали в pop_room.c (банк 7);
|
||||||
|
* граница реза и её цена расписаны в _pop_bg.h.
|
||||||
*
|
*
|
||||||
* Только DUNGEON-стиль (Фаза 1). Анимация (пламя факелов, шипы, дверь-
|
* Подробности алгоритма кладки — в toolchain/render_room.py и
|
||||||
* плита, сама решётка ворот) НЕ рисуется — отдельными sprite_t поверх
|
* memory/pop_background_strategy. Только DUNGEON-стиль (Фаза 1).
|
||||||
* (Фаза 2). Решётка ворот из ЛЕВОЙ комнаты рисуется статичным кадром.
|
|
||||||
*/
|
*/
|
||||||
#include <graphics.h>
|
#include <graphics.h>
|
||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "pop_bg.h"
|
#include "pop_bg.h"
|
||||||
|
#include "_pop_bg.h" /* контракт с холодной половиной (pop_room.c, банк 7) */
|
||||||
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||||||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||||
#include "pop_state.h"
|
#include "pop_state.h"
|
||||||
@@ -23,34 +24,6 @@
|
|||||||
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
|
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
|
||||||
#include "pop_guard.h" /* SWORD_2_DRAWN — расширение футпринта под клинок */
|
#include "pop_guard.h" /* SWORD_2_DRAWN — расширение футпринта под клинок */
|
||||||
|
|
||||||
static const char *const ENV_ATL[5] = {
|
|
||||||
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
|
|
||||||
* KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
|
|
||||||
* зарастает десятками .atl. */
|
|
||||||
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
|
|
||||||
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
|
|
||||||
};
|
|
||||||
|
|
||||||
int pop_bg_load(void) __banked
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < 5; i++)
|
|
||||||
if (atlas_load(&pop_env[i], ENV_ATL[i]) != 0) return -1;
|
|
||||||
if (atlas_load(&pop_wall_at, "BG\\pop_wall.atl") != 0) return -1;
|
|
||||||
if (atlas_load(&pop_fore_at, "BG\\pop_fore.atl") != 0) return -1;
|
|
||||||
if (atlas_load(&pop_pot_at, "BG\\pop_pot.atl") != 0) return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_bg_free(void) __banked
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < 5; i++)
|
|
||||||
atlas_free(&pop_env[i]);
|
|
||||||
atlas_free(&pop_wall_at);
|
|
||||||
atlas_free(&pop_fore_at);
|
|
||||||
atlas_free(&pop_pot_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а
|
/* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а
|
||||||
* draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается
|
* draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается
|
||||||
@@ -85,11 +58,7 @@ void pop_fore_heal(void) __banked
|
|||||||
/* ---- Константные таблицы (render_room.py) -------------------------- */
|
/* ---- Константные таблицы (render_room.py) -------------------------- */
|
||||||
static const uint8_t TBL_LINE[3] = {0, 10, 20};
|
static const uint8_t TBL_LINE[3] = {0, 10, 20};
|
||||||
static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52};
|
static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52};
|
||||||
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
|
||||||
static const uint8_t SPIKES_FRAM_FORE[10] = {0, 139, 140, 141, 142, 143, 142, 140, 139, 0};
|
static const uint8_t SPIKES_FRAM_FORE[10] = {0, 139, 140, 141, 142, 143, 142, 140, 139, 0};
|
||||||
static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
|
|
||||||
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
|
||||||
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45};
|
|
||||||
static const uint8_t LPOS[5] = {58, 41, 37, 20, 16};
|
static const uint8_t LPOS[5] = {58, 41, 37, 20, 16};
|
||||||
static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
||||||
|
|
||||||
@@ -101,30 +70,6 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
|||||||
#define RES_WALL_MARK_TR 16
|
#define RES_WALL_MARK_TR 16
|
||||||
#define RES_WALL_MARK_BR 17
|
#define RES_WALL_MARK_BR 17
|
||||||
|
|
||||||
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
|
|
||||||
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
|
||||||
* склянка (id 13) и pot_size=1, остальные — маленькая (id 12). Спрайты — из
|
|
||||||
* chtab_1 (атлас pop_pot), поэтому НЕ из env: раньше мы рисовали chtab_6 id 12
|
|
||||||
* (res212), которого в VDUNGEON нет — оттуда и «мусорный объект».
|
|
||||||
* Позиции оригинала: склянка xh = fore_x + draw_xh, xl = 6, ybottom = fore_y +
|
|
||||||
* draw_main_y; пузырёк/маска xh = draw_xh + 3, xl = 1,
|
|
||||||
* ybottom = draw_main_y − (pot_size << 2) − 14. */
|
|
||||||
#define POT_MASK_ID 23
|
|
||||||
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
|
|
||||||
|
|
||||||
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
|
|
||||||
|
|
||||||
static void potion_bubble(int row, int col, uint8_t modif, uint8_t fore)
|
|
||||||
{
|
|
||||||
uint8_t type = potion_type(modif);
|
|
||||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
|
||||||
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
|
||||||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
|
||||||
if (type == 0) return;
|
|
||||||
if (fore) pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
|
||||||
else pop_pot_b(POT_MASK_ID, x, yb); /* маска (под ним) */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- *
|
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- *
|
||||||
* Сид (pop_t_seed) живёт в резиденте: его ставит полная отрисовка комнаты, а
|
* Сид (pop_t_seed) живёт в резиденте: его ставит полная отрисовка комнаты, а
|
||||||
* читает кэш раскладки кладки. */
|
* читает кэш раскладки кладки. */
|
||||||
@@ -296,15 +241,6 @@ static void wall_pattern(int row, int col, int which_part)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* topright тайла снизу-слева (seg008.c:410). */
|
|
||||||
static void draw_topright(uint8_t tt, int x, int dby)
|
|
||||||
{
|
|
||||||
if (tt == 7 || tt == 12) return;
|
|
||||||
if (tt == 20) { pop_wall_b(2, x, dby); return; }
|
|
||||||
if (pop_tile_table[tt].topright_id)
|
|
||||||
pop_env_b(pop_tile_table[tt].topright_id, x, dby);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */
|
/* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */
|
||||||
static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
|
static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
|
||||||
{
|
{
|
||||||
@@ -330,774 +266,29 @@ static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
|
|||||||
pop_env_b(DOOR_FRAM_SLICE[gate_frame], x, yb);
|
pop_env_b(DOOR_FRAM_SLICE[gate_frame], x, yb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Loose floors: кадры дрожащей плиты (seg008 loose_fram_*) ------- *
|
|
||||||
* Состояние pop_loose_modif[] живёт в pop_map. draw_tile рисует грани
|
|
||||||
* loose АНИМИРОВАННО по modif (как SDLPoP: сам тайл — лево/низ, ПРАВЫЙ
|
|
||||||
* СОСЕД — правую грань через draw_tile_anim_right). При modif==0 (покой/
|
|
||||||
* статич. запекание) get_loose_frame=0 → базовые кадры 41/42/43. */
|
|
||||||
extern uint8_t pop_loose_modif[30];
|
|
||||||
extern uint8_t pop_ceil_modif[10]; /* плита-потолок: loose ряда 2 комнаты сверху */
|
|
||||||
|
|
||||||
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
|
|
||||||
|
|
||||||
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
|
||||||
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
/* ---- Точки входа для ХОЛОДНОЙ половины (pop_room.c, банк 7) ---------- *
|
||||||
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
* Тонкие обёртки, а не пометка самих тел `__banked`: трамплин выбирается
|
||||||
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
* ОБЪЯВЛЕНИЕМ, поэтому пометка тела заставила бы платить 654 такта и
|
||||||
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Пример: упала плита
|
* ГОРЯЧИХ вызывающих — fore-проход зовёт wall_pattern до девяти раз за кадр.
|
||||||
* (0,6); её запечка перерисовывает и соседа, а правый угол СОСЕДНЕЙ плиты
|
* С обёрткой горячий путь идёт прямым call, а трамплин достаётся только
|
||||||
* (0,5), который рисуется в ячейке (0,6), был в этот момент в тряске.
|
* холодному (полная отрисовка комнаты: ~40 вызовов, 26 000 тактов РАЗОВО на
|
||||||
* Фон обязан быть состоянием ПОКОЯ — анимация каждый кадр кладётся поверх
|
* вход в комнату). См. _pop_bg.h и memory/sdcc_banked_call_rules. */
|
||||||
* (банк SPRITE, только видео-ОЗУ). */
|
void pop_wall_pattern_b(int row, int col, int which_part) __banked
|
||||||
/* draw_leveldoor (seg008:1D29) — дверь уровня. Рисуется при обработке
|
|
||||||
* ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8px (xh+1):
|
|
||||||
* 99 низ лестницы (всегда),
|
|
||||||
* 144 марш лестницы за створкой (когда створка приподнята, modif != 0),
|
|
||||||
* 33 слайс створки — повторяется вниз с шагом 4px, пока не дойдёт до
|
|
||||||
* y = ybottom − modif: modif 0 = закрыто (створка на всю высоту),
|
|
||||||
* 43 = открыто (остаётся только верхняя кромка),
|
|
||||||
* 34 верх коробки двери.
|
|
||||||
* Куски непрозрачные (blitters_0_no_transp) — как в оригинале. */
|
|
||||||
static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
|
||||||
{
|
{
|
||||||
int x = (xh + 1) * 8;
|
wall_pattern(row, col, which_part);
|
||||||
int ybottom = dmy - 13;
|
|
||||||
int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */
|
|
||||||
int y = ybottom - (int)modif;
|
|
||||||
/* leveldoor_right (seg008:1D29): правая кромка проёма — от xh ТЕКУЩЕГО
|
|
||||||
* тайла, то есть ПРАВОЙ половины двери (сюда мы попадаем из
|
|
||||||
* draw_tile_anim_right, разбирая левого соседа). +8 у palace-уровней
|
|
||||||
* (tbl_level_type) — на уровне 1 не применяется. */
|
|
||||||
pop_leveldoor_right = xh * 8 + 48;
|
|
||||||
pop_env_b(99, x, ybottom);
|
|
||||||
if (modif)
|
|
||||||
pop_env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */
|
|
||||||
for (;;) {
|
|
||||||
pop_env_b(33, x, ylo);
|
|
||||||
if (y > ylo) ylo += 4;
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
/* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое
|
|
||||||
* значение (низ поднятой створки), а не стартовое. */
|
|
||||||
pop_leveldoor_ybottom = ylo;
|
|
||||||
pop_env_b(34, x, dmy - 64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
void pop_wall_pattern_reset_b(void) __banked
|
||||||
static void draw_tile(int row, int col)
|
|
||||||
{
|
{
|
||||||
uint8_t code = pop_tile_code(row, col);
|
wall_pattern_reset();
|
||||||
uint8_t lcode = pop_tile_code(row, col - 1);
|
|
||||||
uint8_t lmod = pop_tile_mod(row, col - 1);
|
|
||||||
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
|
|
||||||
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
|
|
||||||
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
|
|
||||||
* соседа (его правая грань). */
|
|
||||||
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
|
||||||
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
|
||||||
if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } }
|
|
||||||
else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; }
|
|
||||||
int xh = POP_COL_XH[col];
|
|
||||||
int x = xh * 8;
|
|
||||||
int dby = 63 * row + 65;
|
|
||||||
int dmy = dby - 3;
|
|
||||||
const piece *t = &pop_tile_table[code];
|
|
||||||
const piece *lt = &pop_tile_table[lcode];
|
|
||||||
uint8_t rbl_code;
|
|
||||||
uint8_t wmod;
|
|
||||||
|
|
||||||
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
|
|
||||||
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] =
|
|
||||||
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
|
|
||||||
if (row + 1 > 2) {
|
|
||||||
int lc = col - 1;
|
|
||||||
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
|
||||||
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
|
||||||
* «стена» там стоит, только если такой комнаты нет — как в
|
|
||||||
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
|
||||||
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
|
||||||
else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 1;
|
|
||||||
} else rbl_code = pop_tile_code(row + 1, col - 1);
|
|
||||||
|
|
||||||
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
|
||||||
if (code == 0 || code == 9 || code == 12 || code == 26) {
|
|
||||||
draw_topright(rbl_code, x, dby);
|
|
||||||
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
|
|
||||||
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
|
|
||||||
* floor_right: lt->floor_right. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- draw_tile_right (seg008.c:456) --- */
|
|
||||||
if (code != 20) {
|
|
||||||
if (lcode == 20) {
|
|
||||||
pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
|
|
||||||
} else if (lcode == 0) {
|
|
||||||
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
|
|
||||||
pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
|
|
||||||
} else if (lcode == 1) {
|
|
||||||
uint8_t num;
|
|
||||||
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
|
|
||||||
num = (lmod <= 3) ? lmod : 0;
|
|
||||||
if (num != 0) /* dungeon: level_type=0 */
|
|
||||||
pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
|
|
||||||
} else {
|
|
||||||
if (lt->right_id)
|
|
||||||
pop_env_b(lt->right_id, x, lt->right_y + dmy);
|
|
||||||
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
|
||||||
if (lcode == 19 || lcode == 30)
|
|
||||||
pop_env_b(146, x, dby - 28);
|
|
||||||
/* stripe — только палас, dungeon пропускает */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
|
||||||
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
|
||||||
* отрицательного индекса в pop_loose_modif. --- */
|
|
||||||
if (row >= 0) {
|
|
||||||
if (lcode == 4) { /* gate: решётка */
|
|
||||||
draw_gate_back(lmod, xh, dby, dmy);
|
|
||||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
|
||||||
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
|
|
||||||
} else if (lcode == 2) { /* spike: статич. кадр */
|
|
||||||
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
|
||||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
|
||||||
pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
|
||||||
} else if (lcode == 16) { /* дверь уровня */
|
|
||||||
draw_leveldoor(lmod, xh, dmy);
|
|
||||||
}
|
|
||||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
|
||||||
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
|
||||||
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- draw_tile_bottom (seg008.c:570) --- */
|
|
||||||
if (code == 20) {
|
|
||||||
wmod = pop_wall_modifier(row, col);
|
|
||||||
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* сплошная нижняя грань */
|
|
||||||
wall_pattern(row, col, 0);
|
|
||||||
} else if (t->bottom_id) {
|
|
||||||
pop_env_b(t->bottom_id, x, dby);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
|
|
||||||
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
|
|
||||||
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
|
|
||||||
if (code == 11)
|
|
||||||
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col]
|
|
||||||
: pop_ceil_modif[col])], x, dby);
|
|
||||||
|
|
||||||
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
|
|
||||||
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
|
|
||||||
if (row >= 0) {
|
|
||||||
uint8_t base_id = t->base_id;
|
|
||||||
if (code == 11) /* loose: левая грань (анимир.) */
|
|
||||||
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
|
|
||||||
else if (code == 0x0F && lcode == 0) /* opener, слева пусто */
|
|
||||||
base_id = 148; /* левая половина без пола */
|
|
||||||
if (base_id)
|
|
||||||
pop_env_b(base_id, x, t->base_y + dmy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
|
||||||
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
|
||||||
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
|
||||||
* пустая. */
|
|
||||||
if (row >= 0 && code == 2) {
|
|
||||||
uint8_t sm = pop_tile_mod(row, col);
|
|
||||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
|
||||||
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
|
||||||
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
|
||||||
}
|
|
||||||
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
|
||||||
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
|
||||||
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
|
||||||
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
|
||||||
if (row >= 0 && code == 22)
|
|
||||||
pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3);
|
|
||||||
|
|
||||||
/* --- draw_tile_fore (seg008.c:690) --- */
|
|
||||||
if (code == 20) {
|
|
||||||
wmod = pop_wall_modifier(row, col);
|
|
||||||
pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
|
|
||||||
wall_pattern(row, col, 1);
|
|
||||||
}
|
|
||||||
if (code == 10) { /* зелье: chtab_1, не env fore_id.
|
|
||||||
* Пузырёк — динамика (pop_potion_draw). */
|
|
||||||
pop_potion_flask(row, col, pop_tile_mod(row, col));
|
|
||||||
} else if (t->fore_id) {
|
|
||||||
pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
|
void pop_gate_back_b(uint8_t modl, int xh, int dby, int dmy) __banked
|
||||||
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
|
||||||
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
|
|
||||||
* внутри draw_tile). */
|
|
||||||
void pop_loose_shake_draw(int row, int col) __banked
|
|
||||||
{
|
{
|
||||||
int x = POP_COL_XH[col] * 8;
|
draw_gate_back(modl, xh, dby, dmy);
|
||||||
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
|
||||||
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
|
|
||||||
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
|
|
||||||
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
|
|
||||||
* решение (bake тайла как empty + рисовать loose всегда динамически)
|
|
||||||
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
|
|
||||||
pop_heal_off(x, 63 * row + 46, 64, 24);
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
draw_tile(row, col); /* loose: лево+низ (анимир.) */
|
|
||||||
if (col + 1 < 10)
|
|
||||||
draw_tile(row, col + 1); /* сосед: правая грань loose (анимир.) + его пол */
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Перерисовать пики тайла (row,col) на back-странице по ЖИВОМУ modif
|
|
||||||
* (pop_trob). Кадр выдвижения рисует ПРАВЫЙ сосед (draw_tile ветка lcode==2
|
|
||||||
* читает lmod = pop_t_bg[row*10+col] = живой modif пики). pop_t_bg должен указывать
|
|
||||||
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
|
||||||
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
|
||||||
void pop_spike_redraw(int row, int col) __banked
|
|
||||||
{
|
|
||||||
int x = POP_COL_XH[col] * 8;
|
|
||||||
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
|
||||||
* redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его
|
|
||||||
* ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя —
|
|
||||||
* порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и
|
|
||||||
* fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну
|
|
||||||
* справа. Заодно draw_tile сам восстанавливает вклад соседних пик:
|
|
||||||
* в ячейке col — правое остриё пики col-1 (ветка lcode==2).
|
|
||||||
* Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх
|
|
||||||
* (63*row+34 .. +74); ширина 64 = обе ячейки. */
|
|
||||||
pop_heal_off(x, 63 * row + 34, 64, 40);
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
draw_tile(row, col);
|
|
||||||
if (col + 1 < 10)
|
|
||||||
draw_tile(row, col + 1);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
|
|
||||||
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
|
|
||||||
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]).
|
|
||||||
* pop_t_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
|
|
||||||
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
|
||||||
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
|
||||||
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
|
||||||
void pop_room_redraw_seam_left(void) __banked
|
|
||||||
{
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
|
|
||||||
* а бары ворот начинаются с y=3 (gate_top_y = dby−62). Раньше bar шёл от
|
|
||||||
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
|
|
||||||
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
|
|
||||||
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
|
|
||||||
* В полосе кладки у потолка (наш ряд −1) виден только НИЗ плиты
|
|
||||||
* (draw_tile_aboveroom: bottom+loose, всё выше отрезано клипом POP_YOFF).
|
|
||||||
* Дрожащий кадр берётся из pop_ceil_modif[col] (см. draw_loose выше).
|
|
||||||
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
|
|
||||||
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
|
||||||
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
|
|
||||||
|
|
||||||
void pop_ceil_shake_draw(int col) __banked
|
|
||||||
{
|
|
||||||
int x = POP_COL_XH[col] * 8;
|
|
||||||
pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
pop_t_clip_top = POP_YOFF;
|
|
||||||
draw_tile(-1, col);
|
|
||||||
if (col + 1 < 10) draw_tile(-1, col + 1);
|
|
||||||
pop_t_clip_top = 0;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_ceil_bake_empty(int col) __banked
|
|
||||||
{
|
|
||||||
int x = POP_COL_XH[col] * 8;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(x, POP_YOFF, x + 63, POP_YOFF + CEIL_BAND_H); /* стереть плиту в ОЗУ-копии */
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
pop_t_clip_top = POP_YOFF;
|
|
||||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
|
||||||
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
|
||||||
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
|
||||||
* нижней грани и свисают ВПРАВО, поэтому в полосу над тайлом col лезут:
|
|
||||||
* • ряд −1, колонки col−1..col+1 — сама полоса кладки у потолка;
|
|
||||||
* • ряд 0, колонки col−1..col+1 — их верхушки заходят в те же 8 px.
|
|
||||||
* Раньше перерисовывались только (−1,col) и (−1,col+1), и в полосе
|
|
||||||
* оставался чёрный прямоугольник ~13×3 px (замер: экранные y 47..52 при
|
|
||||||
* полосе 44..52), который heal возвращал каждый кадр. Признак, что дело
|
|
||||||
* именно в запечке: после выхода из комнаты и возврата бара нет. */
|
|
||||||
{
|
|
||||||
int c0 = (col > 0) ? col - 1 : 0;
|
|
||||||
int c1 = (col + 1 < 9) ? col + 1 : 9;
|
|
||||||
int c;
|
|
||||||
for (c = c0; c <= c1; c++) draw_tile(-1, c);
|
|
||||||
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
|
||||||
}
|
|
||||||
pop_t_bake_rest = 0;
|
|
||||||
pop_t_clip_top = 0;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
|
|
||||||
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
|
|
||||||
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
|
|
||||||
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
|
|
||||||
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
|
||||||
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
|
||||||
* (pot_size<<2) − 14 (seg008:668). */
|
|
||||||
void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
|
||||||
{
|
|
||||||
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = POP_COL_XH[col+1] + 1
|
|
||||||
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
|
||||||
* 146), которое тоже лежит в ячейке соседа. */
|
|
||||||
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
|
||||||
int yb = 63 * row + 62 - 40;
|
|
||||||
if (modif >= 9) return;
|
|
||||||
/* 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);
|
|
||||||
pop_pot_b((uint8_t)(modif + 1), x, yb);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
|
||||||
{
|
|
||||||
uint8_t type = potion_type(modif);
|
|
||||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
|
||||||
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
|
||||||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
|
||||||
if (type == 0) return;
|
|
||||||
pop_heal_off(x - 1, yb - 10, 8, 12);
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
|
||||||
pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
|
|
||||||
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
|
||||||
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
|
||||||
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
|
||||||
void pop_floor_bake(int row, int col) __banked
|
|
||||||
{
|
|
||||||
int x = POP_COL_XH[col] * 8;
|
|
||||||
int yb = POP_YOFF + 63 * row;
|
|
||||||
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
|
||||||
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
|
||||||
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(x, yb + 26, x + 59, yb + 64);
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
|
||||||
draw_tile(row, col);
|
|
||||||
if (col + 1 < 10) draw_tile(row, col + 1);
|
|
||||||
pop_t_bake_rest = 0;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
|
|
||||||
|
|
||||||
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
|
||||||
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
|
||||||
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
|
||||||
* Куски двери непрозрачные — wipe не нужен. */
|
|
||||||
void pop_leveldoor_redraw(int row, int col) __banked
|
|
||||||
{
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
draw_tile(row, col);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ворота ВНУТРИ комнаты: перерисовать бары по живому modif (порт draw_trob,
|
|
||||||
* seg007:01E6 — им заканчивается animate_door). (row,col) — сами ворота;
|
|
||||||
* решётку рисует ячейка ПРАВОГО соседа (draw_tile, ветка lcode==4), поэтому
|
|
||||||
* wipe и redraw идут по ней. Логика ровно как у левого шва
|
|
||||||
* (pop_room_redraw_seam_left), только колонка произвольная.
|
|
||||||
*
|
|
||||||
* Bar-в-чёрный обязателен: бары висят в видео-ОЗУ и ОЗУ-копии, и при
|
|
||||||
* ПОДЪЁМЕ решётки heal вернул бы запечённые нижние бары. Полоса — ровно
|
|
||||||
* зона решётки: от gate_top_y (dby−62 = 63*row+3) до низа (63*row+64);
|
|
||||||
* выше начинается ряд −1, ниже — стена ряда ниже.
|
|
||||||
*
|
|
||||||
* Ворота в col9: их бары рисуются уже в СОСЕДНЕЙ комнате, в отрисованной их
|
|
||||||
* не видно — оригинал такой trob тоже пропускает
|
|
||||||
* (get_trob_right_pos_in_drawn_room → 30). Для взгляда из комнаты справа
|
|
||||||
* работает pop_room_redraw_seam_left. */
|
|
||||||
void pop_gate_redraw(int row, int col) __banked
|
|
||||||
{
|
|
||||||
int x, yt;
|
|
||||||
if (col + 1 > 9 || row < 0 || row > 2) return;
|
|
||||||
x = POP_COL_XH[col + 1] * 8;
|
|
||||||
yt = POP_YOFF + 63 * row;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(x, yt + 3, x + 31, yt + 64);
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
draw_tile(row, col + 1);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
|
||||||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
|
||||||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
|
||||||
* рисовал loose-правый-край pop_env_b(42); теперь пусто → чистая грань).
|
|
||||||
* pop_t_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
|
||||||
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
|
||||||
void pop_loose_bake_empty(int row, int col) __banked
|
|
||||||
{
|
|
||||||
int x = POP_COL_XH[col] * 8;
|
|
||||||
int xr = POP_COL_XH[col + 1] * 8;
|
|
||||||
int yb = 63 * row + 46;
|
|
||||||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
|
||||||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
|
||||||
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
|
|
||||||
* заходит на x+32..+40. */
|
|
||||||
/* НИЗ строго 63*row+65 = низ собственной графики плиты (bottom_id 43/73/74
|
|
||||||
* стоит ровно на dby; base 41/69/70 — до dmy=62; правая грань 42/66/67 —
|
|
||||||
* до dby-1). Ниже начинается СТЕНА ряда ниже (высокий спрайт), которую
|
|
||||||
* здесь не перерисовываем: заливка на 9 строк глубже оставляла ЧЁРНЫЙ БОКС
|
|
||||||
* под соседним столбом, причём в ОЗУ-копии — то есть heal возвращал его
|
|
||||||
* снова, и на второй странице дабл-буфера его не было → мерцание.
|
|
||||||
* Тот же урок уже учтён в pop_floor_bake. */
|
|
||||||
yb += POP_YOFF;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
setfillstyle(SOLID_FILL, BLACK);
|
|
||||||
bar(x, yb, x + 39, POP_YOFF + 63 * row + 65); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
|
||||||
if (col + 1 < 10)
|
|
||||||
bar(xr, yb, xr + 31, POP_YOFF + 63 * row + 65); /* сосед: РОВНО 32 — не залезть в col+2 (его не перерисовываем) */
|
|
||||||
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
|
||||||
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
|
||||||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
|
||||||
* ОЗУ-копию → heal сохранит. */
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
|
||||||
draw_tile(row, col);
|
|
||||||
if (col + 1 < 10)
|
|
||||||
draw_tile(row, col + 1);
|
|
||||||
pop_t_bake_rest = 0;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Падающий кусок loose (mob, seg007 move_loose) ------------------ *
|
|
||||||
* Упрощ. одиночный кусок (в комнате 1 — один loose). Модель fixed-
|
|
||||||
* corridor: коридор падения (колонка тайла) heal'ится каждый кадр (стирает
|
|
||||||
* прошлый кусок, возвращает печёный фон — empty+«стена вниз»), поверх —
|
|
||||||
* кадр 0 loose (41/42/43) на текущей y. Гравитация: speed 0→+3/кадр
|
|
||||||
* (cap 29), y+=speed. Двоебуфер: коридор фиксирован → heal чистит любой
|
|
||||||
* прошлый кадр ЭТОЙ страницы. Ниже поля кусок прячет clip_borders. */
|
|
||||||
/* Падающие куски — ТАБЛИЦА (порт mobs[14]/mobs_count, seg007:965). Одного
|
|
||||||
* куска не хватает: в комнате 7 плиты (0,5) и (0,6) соседние, Kid роняет обе
|
|
||||||
* подряд, и второй спавн затирал состояние первого — его пиксели оставались
|
|
||||||
* висеть в воздухе (на каждой странице на своей y → мерцание), а щебень
|
|
||||||
* появлялся только под одной плитой. 4 слота: больше одновременно падающих
|
|
||||||
* плит в одной комнате практически не бывает (в оригинале 14). */
|
|
||||||
#define MOB_MAX 4
|
|
||||||
typedef struct {
|
|
||||||
uint8_t active; /* 0 нет; 1 падает */
|
|
||||||
uint8_t clean; /* кадров дочистки после конца полёта (2 = обе стр.) */
|
|
||||||
uint8_t defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
|
|
||||||
int x; /* левый край (POP_COL_XH*8) */
|
|
||||||
int y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
|
|
||||||
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
|
|
||||||
int8_t speed;
|
|
||||||
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
|
|
||||||
uint8_t room; /* curmob.room: кусок падает В СВОЕЙ комнате, не в текущей */
|
|
||||||
} mob_t;
|
|
||||||
static mob_t mobs[MOB_MAX];
|
|
||||||
|
|
||||||
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
|
||||||
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
|
||||||
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
|
|
||||||
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
|
|
||||||
|
|
||||||
/* y_loose_land / y_something (seg007): [row+1], row = −1..3. Ряд −1 —
|
|
||||||
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
|
|
||||||
* tilepos, у нас — свой ряд полосы у потолка. */
|
|
||||||
static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
|
||||||
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
|
||||||
#define MOB_Y_NONE 0x7FFF
|
|
||||||
|
|
||||||
void pop_loose_mob_spawn(int row, int col) __banked
|
|
||||||
{
|
|
||||||
uint8_t i;
|
|
||||||
mob_t *m = 0;
|
|
||||||
/* свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
|
||||||
* который ещё не стёрт со второй страницы) */
|
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
|
||||||
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
|
|
||||||
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
|
||||||
m->x = POP_COL_XH[col] * 8;
|
|
||||||
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
|
||||||
m->row = (int8_t)row;
|
|
||||||
m->col = (int8_t)col;
|
|
||||||
m->room = pop_t_room; /* curmob.room (add_mob, seg007:1010) */
|
|
||||||
m->speed = 0;
|
|
||||||
m->active = 1;
|
|
||||||
m->clean = 0;
|
|
||||||
m->defer = 0;
|
|
||||||
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
|
|
||||||
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
|
|
||||||
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
|
|
||||||
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
|
|
||||||
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
|
|
||||||
#define MOB_X0(mx) ((mx) - 4)
|
|
||||||
#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */
|
|
||||||
|
|
||||||
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
|
|
||||||
* должно утечь в новую комнату). */
|
|
||||||
/* Тайл ОБЪЕКТА Kid (set_objtile_at_char, seg006:1833) — в момент обработки
|
|
||||||
* этого тайла спрайт Kid попадает в midtable. Задаётся в pop_fore_over_char,
|
|
||||||
* читается other_overlay_tile (порядок «кто поверх кого»). */
|
|
||||||
static int8_t obj_col, obj_row;
|
|
||||||
|
|
||||||
static void mob_render(mob_t *m, uint8_t pg);
|
|
||||||
|
|
||||||
void pop_loose_mob_reset(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t i;
|
|
||||||
for (i = 0; i < MOB_MAX; i++) {
|
|
||||||
mobs[i].active = 0; mobs[i].clean = 0; mobs[i].defer = 0;
|
|
||||||
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Смена комнаты. Куски НЕ останавливаем: в оригинале mobs[] чистится только
|
|
||||||
* в start_level (seg003:88), а do_mobs (seg007:1063) крутит их все независимо
|
|
||||||
* от drawn_room — move_loose работает по curmob.room, и плита, за которой Кид
|
|
||||||
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
|
|
||||||
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
|
|
||||||
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
|
|
||||||
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;
|
|
||||||
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
|
|
||||||
if (!mobs[i].active) mobs[i].clean = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
|
|
||||||
* и соседей), в чужой — прямо из данных уровня. */
|
|
||||||
static uint8_t mob_tile_at(mob_t *m)
|
|
||||||
{
|
|
||||||
if (m->room == pop_t_room) return (uint8_t)(pop_tile_code(m->row, m->col) & 0x1F);
|
|
||||||
if (m->row < 0 || m->row > 2 || m->col < 0 || m->col > 9) return 1; /* кромка = пол */
|
|
||||||
return (uint8_t)(pop_level_tile(m->room, (uint8_t)(m->row * 10 + m->col)) & 0x1F);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mob_tick_one(mob_t *m, uint8_t pg)
|
|
||||||
{
|
|
||||||
uint8_t here;
|
|
||||||
if (!m->active && !m->clean) return;
|
|
||||||
here = (uint8_t)(m->room == pop_t_room);
|
|
||||||
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
|
||||||
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
|
||||||
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
|
|
||||||
* прошлой позиции. */
|
|
||||||
if (here && m->prev_y[pg] != MOB_Y_NONE) {
|
|
||||||
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
|
|
||||||
m->prev_y[pg] = MOB_Y_NONE;
|
|
||||||
}
|
|
||||||
if (!m->active) { m->clean--; return; }
|
|
||||||
/* гравитация (move_loose): ускорение до 29 */
|
|
||||||
if (m->speed < 29) m->speed += 3;
|
|
||||||
m->y += m->speed;
|
|
||||||
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
|
|
||||||
* смотрим тайл этого ряда. Пусто/loose — валится дальше (порт
|
|
||||||
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
|
|
||||||
* пол — приземление (loose_land: debris + do_knock). */
|
|
||||||
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
|
|
||||||
uint8_t t = mob_tile_at(m);
|
|
||||||
if (t == 0 || t == 11) {
|
|
||||||
m->row++; /* mob_down_a_row */
|
|
||||||
} else if (!here) {
|
|
||||||
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, сигнальная
|
|
||||||
* пара pop_loose_landed/pop_debris_at обслуживает только текущую
|
|
||||||
* комнату — щебень кладём прямо в данные уровня (loose_land пишет
|
|
||||||
* в curr_room_tiles комнаты curmob.room, а не отрисованной). */
|
|
||||||
pop_level_set_tile(m->room, (uint8_t)(m->row * 10 + m->col), 0x0E);
|
|
||||||
m->active = 0; m->clean = 0;
|
|
||||||
return;
|
|
||||||
} else if (pop_loose_landed) {
|
|
||||||
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
|
|
||||||
* подержим кусок на границе и приземлим следующим кадром */
|
|
||||||
m->y = MOB_Y_BOUND[m->row + 1];
|
|
||||||
} else {
|
|
||||||
m->y = MOB_Y_BOUND[m->row + 1];
|
|
||||||
pop_loose_landed = (uint8_t)(m->row * 10 + m->col + 1);
|
|
||||||
m->active = 0; m->clean = 2; /* кусок исчез — на его месте debris */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
|
|
||||||
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!here) return; /* чужая комната — считаем, но не рисуем */
|
|
||||||
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
|
|
||||||
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
|
|
||||||
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
|
|
||||||
* ниже — тот позже, т.е. поверх). Тайл падающего куска — draw_mob
|
|
||||||
* (seg007:13E5): row = y_to_row_mod4(y), col = xh>>2. Значит кусок
|
|
||||||
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
|
|
||||||
* совпадении тайлов — если он ниже по y. */
|
|
||||||
{
|
|
||||||
int8_t mrow = (int8_t)(((m->y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */
|
|
||||||
uint8_t over;
|
|
||||||
if (mrow != obj_row) over = (mrow < obj_row); /* ряды идут 2,1,0 */
|
|
||||||
else if (m->col != obj_col) over = (m->col > obj_col);
|
|
||||||
else over = (m->y > pop_cd[POP_CH_KID].fpy);
|
|
||||||
if (over) { m->defer = 1; return; }
|
|
||||||
}
|
|
||||||
mob_render(m, pg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_loose_mob_tick(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
|
||||||
mob_tick_one(&mobs[i], pg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mob_render(mob_t *m, uint8_t pg)
|
|
||||||
{
|
|
||||||
/* Падающий кусок (SDLPoP draw_mobs/add_midtable, case 0x80): ТРИ куска —
|
|
||||||
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
|
|
||||||
* заходит в тайл СПРАВА) на obj_y-1. */
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
pop_env_b(43, m->x, m->y);
|
|
||||||
pop_env_b(41, m->x, m->y - 3);
|
|
||||||
pop_env_b(42, m->x + 32, m->y - 1);
|
|
||||||
m->prev_y[pg] = m->y;
|
|
||||||
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
|
|
||||||
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
|
|
||||||
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
|
|
||||||
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
|
|
||||||
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
|
|
||||||
* до heal'а следующего кадра — виден как хвост во время падения). */
|
|
||||||
if (m->col + 1 < 10) {
|
|
||||||
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
|
|
||||||
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
|
|
||||||
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
|
|
||||||
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
|
|
||||||
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
|
|
||||||
* через пол + перекрыта передняя грань пола.
|
|
||||||
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
|
|
||||||
* границе рядов. Порт draw_mob (seg007:13E5). */
|
|
||||||
int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1);
|
|
||||||
int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1);
|
|
||||||
if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */
|
|
||||||
draw_tile(r, m->col + 1);
|
|
||||||
pop_t_clip_top = 0;
|
|
||||||
if (rt != r) {
|
|
||||||
if (rt < 0) pop_t_clip_top = POP_YOFF;
|
|
||||||
draw_tile(rt, m->col + 1);
|
|
||||||
pop_t_clip_top = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
|
|
||||||
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
|
||||||
* после отрисовки персонажей, до fore-слоя. */
|
|
||||||
void pop_loose_mob_draw_over(void) __banked
|
|
||||||
{
|
|
||||||
uint8_t pg = gfx_get_draw_page() & 1, i;
|
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
|
||||||
if (mobs[i].defer) { mobs[i].defer = 0; mob_render(&mobs[i], pg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
|
||||||
* y (низ куска). 0 — куска нет. */
|
|
||||||
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
|
|
||||||
{
|
|
||||||
uint8_t i;
|
|
||||||
/* check_loose_fall_on_kid (seg007:1192) начинается с Char.room ==
|
|
||||||
* curmob.room: кусок, падающий в покинутой комнате, Киду по голове не
|
|
||||||
* бьёт. */
|
|
||||||
for (i = 0; i < MOB_MAX; i++)
|
|
||||||
if (mobs[i].active && mobs[i].room == pop_t_room) {
|
|
||||||
*col = mobs[i].col; *y = mobs[i].y; return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Спрайт вышел за поле? → взвести флаг на 2 кадра (обе страницы дабл-
|
|
||||||
* буфера). Узкий per-спрайт heal НЕ используем: accel-выравнивание блока
|
|
||||||
* оставляет 1–2px слайверы правой грани → на одной странице мерцает.
|
|
||||||
* Вместо этого борта чистит pop_room_clip_borders (полоса, но ТОЛЬКО в
|
|
||||||
* кадрах падения — гейт по флагу). */
|
|
||||||
static uint8_t border_dirty;
|
|
||||||
|
|
||||||
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
|
|
||||||
{
|
|
||||||
int sy_bot = sy_top + h;
|
|
||||||
(void)x; (void)w;
|
|
||||||
if (sy_bot > POP_CLIP_BOTTOM || sy_top < POP_YOFF)
|
|
||||||
border_dirty = 2; /* 2 кадра = обе страницы */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Клип бортов (см. pop_bg.h): работает ТОЛЬКО пока border_dirty (кто-то
|
|
||||||
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
|
|
||||||
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
|
|
||||||
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
|
|
||||||
void pop_room_clip_borders(void) __banked
|
|
||||||
{
|
|
||||||
if (!border_dirty) return;
|
|
||||||
border_dirty--;
|
|
||||||
/* ОСТАЁТСЯ клипающим осознанно (CLIP-1, 2026-08-01): полоса шириной 320
|
|
||||||
* не лезет в 8-битный параметр noclip-ядра, а разбивать её на два куска
|
|
||||||
* по 160 нет смысла — гейт border_dirty пускает сюда только в кадрах
|
|
||||||
* падения/выхода за поле, и выигрыш (~2×5.6 К тактов подготовки) там
|
|
||||||
* тонет в цене самих 320×28 пикселей. Не «оптимизировать» повторно. */
|
|
||||||
gfx_heal(0, 0, 320, POP_YOFF); /* верхний борт */
|
|
||||||
gfx_heal(0, POP_CLIP_BOTTOM, 320, 256 - POP_CLIP_BOTTOM);/* статус-полоса + нижний борт */
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_room_draw(uint8_t room_num,
|
|
||||||
const uint8_t *fg, const uint8_t *bg,
|
|
||||||
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
|
|
||||||
{
|
|
||||||
int row, col;
|
|
||||||
pop_t_room = room_num;
|
|
||||||
wall_pattern_reset(); /* кэш кладки привязан к номеру комнаты */
|
|
||||||
pop_t_fg = fg; pop_t_bg = bg;
|
|
||||||
pop_t_lfg = lcol_fg; pop_t_lbg = lcol_bg;
|
|
||||||
|
|
||||||
/* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */
|
|
||||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
|
||||||
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
|
||||||
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
|
||||||
for (col = 0; col < 10; col++)
|
|
||||||
draw_tile(row, col);
|
|
||||||
/* Полоса кладки у потолка (seg008 draw_room: доп. ряд из room_A, row2
|
|
||||||
* при draw_main_y=-1): нижние/правые грани ряда 2 комнаты сверху. Кадры
|
|
||||||
* высокие — верх ушёл бы в борт (за поле); клип по верхней границе поля
|
|
||||||
* (POP_YOFF) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
|
|
||||||
pop_t_clip_top = POP_YOFF;
|
|
||||||
for (col = 0; col < 10; col++)
|
|
||||||
draw_tile(-1, col);
|
|
||||||
pop_t_clip_top = 0;
|
|
||||||
pop_t_bake_rest = 0;
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- fore-over-Kid (порт seg003 redraw_at_char + seg008 set_char_collision) --
|
/* ---- fore-over-Kid (порт seg003 redraw_at_char + seg008 set_char_collision) --
|
||||||
@@ -1206,6 +397,9 @@ static void fore_tile(int row, int col) /* передний слой одно
|
|||||||
fore_only_tile(row, col);
|
fore_only_tile(row, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Определение общих с pop_room.c obj-координат (объявление — _pop_bg.h). */
|
||||||
|
int8_t pop_bg_obj_col, pop_bg_obj_row;
|
||||||
|
|
||||||
/* Пол-оверлей при подъёме (draw_floor_overlay, seg008:1457): когда Kid
|
/* Пол-оверлей при подъёме (draw_floor_overlay, seg008:1457): когда Kid
|
||||||
* подтягивается на floor-подобный тайл назначения (floor/pillar/stuck/torch),
|
* подтягивается на floor-подобный тайл назначения (floor/pillar/stuck/torch),
|
||||||
* а тайл СЛЕВА пуст (т.е. кромка), передний край пола перекрывает нижнюю
|
* а тайл СЛЕВА пуст (т.е. кромка), передний край пола перекрывает нижнюю
|
||||||
@@ -1322,7 +516,7 @@ static void other_overlay_tile(int row, int col)
|
|||||||
* спрайтом его перерисовывать не нужно. */
|
* спрайтом его перерисовывать не нужно. */
|
||||||
if (code == 0) return;
|
if (code == 0) return;
|
||||||
/* Тайл идёт в обходе РАНЬШЕ тайла Kid → его оверлей лёг бы ПОД спрайт. */
|
/* Тайл идёт в обходе РАНЬШЕ тайла Kid → его оверлей лёг бы ПОД спрайт. */
|
||||||
if (row > obj_row || (row == obj_row && col <= obj_col)) return;
|
if (row > pop_bg_obj_row || (row == pop_bg_obj_row && col <= pop_bg_obj_col)) return;
|
||||||
left = pop_tile_code(row, col - 1);
|
left = pop_tile_code(row, col - 1);
|
||||||
if (left == 0) { overlay_mid_tile(row, col); return; } /* ветка 1: сосед слева пуст */
|
if (left == 0) { overlay_mid_tile(row, col); return; } /* ветка 1: сосед слева пуст */
|
||||||
if (col > 0 && pop_tile_code(row, col - 2) == 0) {
|
if (col > 0 && pop_tile_code(row, col - 2) == 0) {
|
||||||
@@ -1331,7 +525,7 @@ static void other_overlay_tile(int row, int col)
|
|||||||
* tilepos рисуются СРАЗУ ПОСЛЕ оверлея — Kid возвращается наверх.
|
* tilepos рисуются СРАЗУ ПОСЛЕ оверлея — Kid возвращается наверх.
|
||||||
* Без этого правая грань соседнего столба (кадр 93) остаётся поверх
|
* Без этого правая грань соседнего столба (кадр 93) остаётся поверх
|
||||||
* прыгающего Kid (баг «тень на дальнем столбе поверх Кида»). */
|
* прыгающего Kid (баг «тень на дальнем столбе поверх Кида»). */
|
||||||
if (row == obj_row && (col == obj_col + 1)) return;
|
if (row == pop_bg_obj_row && (col == pop_bg_obj_col + 1)) return;
|
||||||
overlay_mid_tile(row, col);
|
overlay_mid_tile(row, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1442,12 +636,12 @@ void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y,
|
|||||||
* попадает в midtable — база порядка окклюзии в other_overlay_tile.
|
* попадает в midtable — база порядка окклюзии в other_overlay_tile.
|
||||||
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
|
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
|
||||||
* подъём — колонка на одну ЛЕВЕЕ curr_col. */
|
* подъём — колонка на одну ЛЕВЕЕ curr_col. */
|
||||||
obj_row = ch->curr_row;
|
pop_bg_obj_row = ch->curr_row;
|
||||||
obj_col = ch->curr_col;
|
pop_bg_obj_col = ch->curr_col;
|
||||||
if (action == 1) { obj_row = rB; obj_col = cL; }
|
if (action == 1) { pop_bg_obj_row = rB; pop_bg_obj_col = cL; }
|
||||||
if ((frame >= 135 && frame < 149) ||
|
if ((frame >= 135 && frame < 149) ||
|
||||||
action == 2 || action == 3 || action == 4 || action == 6)
|
action == 2 || action == 3 || action == 4 || action == 6)
|
||||||
obj_col--;
|
pop_bg_obj_col--;
|
||||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, heal восстановит */
|
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, heal восстановит */
|
||||||
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
|
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
|
||||||
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
|
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
|
||||||
|
|||||||
@@ -0,0 +1,846 @@
|
|||||||
|
/*
|
||||||
|
* pop_room.c — ХОЛОДНАЯ половина слоя фона PoP: полная отрисовка комнаты
|
||||||
|
* (порт seg008.c draw_tile / render_room.py), точечные перерисовки тайлов
|
||||||
|
* при событиях (пики, ворота, кнопки, дверь уровня, дрожащие плиты) и
|
||||||
|
* падающие куски loose (mob, seg007 move_loose). Всё это работает раз на
|
||||||
|
* комнату или раз на событие, поэтому живёт в ОТДЕЛЬНОМ банке и платит
|
||||||
|
* трамплин за вызовы горячей половины — см. _pop_bg.h, там же граница реза.
|
||||||
|
*
|
||||||
|
* Только DUNGEON-стиль (Фаза 1).
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "pop_bg.h"
|
||||||
|
#include "_pop_bg.h" /* контракт с горячей половиной (pop_bg.c, банк 2) */
|
||||||
|
#include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */
|
||||||
|
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||||
|
#include "pop_state.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
|
||||||
|
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */
|
||||||
|
#include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */
|
||||||
|
|
||||||
|
static const char *const ENV_ATL[5] = {
|
||||||
|
/* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
|
||||||
|
* KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
|
||||||
|
* зарастает десятками .atl. */
|
||||||
|
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
|
||||||
|
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
|
||||||
|
};
|
||||||
|
|
||||||
|
int pop_bg_load(void) __banked
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 5; i++)
|
||||||
|
if (atlas_load(&pop_env[i], ENV_ATL[i]) != 0) return -1;
|
||||||
|
if (atlas_load(&pop_wall_at, "BG\\pop_wall.atl") != 0) return -1;
|
||||||
|
if (atlas_load(&pop_fore_at, "BG\\pop_fore.atl") != 0) return -1;
|
||||||
|
if (atlas_load(&pop_pot_at, "BG\\pop_pot.atl") != 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_bg_free(void) __banked
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 5; i++)
|
||||||
|
atlas_free(&pop_env[i]);
|
||||||
|
atlas_free(&pop_wall_at);
|
||||||
|
atlas_free(&pop_fore_at);
|
||||||
|
atlas_free(&pop_pot_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t SPIKES_FRAM_RIGHT[10] = {0, 134, 135, 136, 137, 138, 137, 135, 134, 0};
|
||||||
|
|
||||||
|
static const uint8_t BLUELINE_FRAM1[4] = {0, 124, 125, 126};
|
||||||
|
static const int8_t BLUELINE_FRAM_Y[4] = {0, -20, -20, 0};
|
||||||
|
static const uint8_t BLUELINE_FRAM3[4] = {44, 44, 45, 45};
|
||||||
|
|
||||||
|
/* ---- Зелье (tiles_10_potion, seg008 draw_tile_anim/draw_tile_fore) ----
|
||||||
|
* Тип = (modif & 0xF8) >> 3: 0 = пусто (ничего не рисуем), 2..4 — «большая»
|
||||||
|
* склянка (id 13) и pot_size=1, остальные — маленькая (id 12). Спрайты — из
|
||||||
|
* chtab_1 (атлас pop_pot), поэтому НЕ из env: раньше мы рисовали chtab_6 id 12
|
||||||
|
* (res212), которого в VDUNGEON нет — оттуда и «мусорный объект».
|
||||||
|
* Позиции оригинала: склянка xh = fore_x + draw_xh, xl = 6, ybottom = fore_y +
|
||||||
|
* draw_main_y; пузырёк/маска xh = draw_xh + 3, xl = 1,
|
||||||
|
* ybottom = draw_main_y − (pot_size << 2) − 14. */
|
||||||
|
#define POT_MASK_ID 23
|
||||||
|
static const uint8_t POTION_FRAM_BUBB[8] = {0, 16, 17, 18, 19, 20, 21, 22};
|
||||||
|
|
||||||
|
static uint8_t potion_type(uint8_t modif) { return (uint8_t)((modif & 0xF8) >> 3); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* topright тайла снизу-слева (seg008.c:410). */
|
||||||
|
static void draw_topright(uint8_t tt, int x, int dby)
|
||||||
|
{
|
||||||
|
if (tt == 7 || tt == 12) return;
|
||||||
|
if (tt == 20) { pop_wall_b(2, x, dby); return; }
|
||||||
|
if (pop_tile_table[tt].topright_id)
|
||||||
|
pop_env_b(pop_tile_table[tt].topright_id, x, dby);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- Loose floors: кадры дрожащей плиты (seg008 loose_fram_*) ------- *
|
||||||
|
* Состояние pop_loose_modif[] живёт в pop_map. draw_tile рисует грани
|
||||||
|
* loose АНИМИРОВАННО по modif (как SDLPoP: сам тайл — лево/низ, ПРАВЫЙ
|
||||||
|
* СОСЕД — правую грань через draw_tile_anim_right). При modif==0 (покой/
|
||||||
|
* статич. запекание) get_loose_frame=0 → базовые кадры 41/42/43. */
|
||||||
|
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
|
||||||
|
|
||||||
|
/* Режим ЗАПЕЧКИ статического фона (запись идёт и в ОЗУ-копию акселератора,
|
||||||
|
* из которой потом восстанавливает heal). В этом режиме дрожащие кадры
|
||||||
|
* ЧУЖИХ плит рисовать НЕЛЬЗЯ: транзиентный кадр законсервируется в копии, а
|
||||||
|
* так как запечка идёт двумя кадрами (по одному на страницу дабл-буфера),
|
||||||
|
* на страницах застынут РАЗНЫЕ кадры — вечное мерцание. Пример: упала плита
|
||||||
|
* (0,6); её запечка перерисовывает и соседа, а правый угол СОСЕДНЕЙ плиты
|
||||||
|
* (0,5), который рисуется в ячейке (0,6), был в этот момент в тряске.
|
||||||
|
* Фон обязан быть состоянием ПОКОЯ — анимация каждый кадр кладётся поверх
|
||||||
|
* (банк SPRITE, только видео-ОЗУ). */
|
||||||
|
/* draw_leveldoor (seg008:1D29) — дверь уровня. Рисуется при обработке
|
||||||
|
* ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8px (xh+1):
|
||||||
|
* 99 низ лестницы (всегда),
|
||||||
|
* 144 марш лестницы за створкой (когда створка приподнята, modif != 0),
|
||||||
|
* 33 слайс створки — повторяется вниз с шагом 4px, пока не дойдёт до
|
||||||
|
* y = ybottom − modif: modif 0 = закрыто (створка на всю высоту),
|
||||||
|
* 43 = открыто (остаётся только верхняя кромка),
|
||||||
|
* 34 верх коробки двери.
|
||||||
|
* Куски непрозрачные (blitters_0_no_transp) — как в оригинале. */
|
||||||
|
static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
||||||
|
{
|
||||||
|
int x = (xh + 1) * 8;
|
||||||
|
int ybottom = dmy - 13;
|
||||||
|
int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */
|
||||||
|
int y = ybottom - (int)modif;
|
||||||
|
/* leveldoor_right (seg008:1D29): правая кромка проёма — от xh ТЕКУЩЕГО
|
||||||
|
* тайла, то есть ПРАВОЙ половины двери (сюда мы попадаем из
|
||||||
|
* draw_tile_anim_right, разбирая левого соседа). +8 у palace-уровней
|
||||||
|
* (tbl_level_type) — на уровне 1 не применяется. */
|
||||||
|
pop_leveldoor_right = xh * 8 + 48;
|
||||||
|
pop_env_b(99, x, ybottom);
|
||||||
|
if (modif)
|
||||||
|
pop_env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */
|
||||||
|
for (;;) {
|
||||||
|
pop_env_b(33, x, ylo);
|
||||||
|
if (y > ylo) ylo += 4;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
/* ИМЕННО после цикла: в оригинале clip_char читает уже доведённое
|
||||||
|
* значение (низ поднятой створки), а не стартовое. */
|
||||||
|
pop_leveldoor_ybottom = ylo;
|
||||||
|
pop_env_b(34, x, dmy - 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||||||
|
static void draw_tile(int row, int col)
|
||||||
|
{
|
||||||
|
uint8_t code = pop_tile_code(row, col);
|
||||||
|
uint8_t lcode = pop_tile_code(row, col - 1);
|
||||||
|
uint8_t lmod = pop_tile_mod(row, col - 1);
|
||||||
|
/* Нажатая кнопка (seg008 get_tile_to_draw): при таймере связи >1 opener
|
||||||
|
* (0x0F) рисуется как ПОЛ (modif 0), closer (0x06) — как stuck (0x05).
|
||||||
|
* Модификатор кнопки = индекс LINKLOC. И для самого тайла, и для левого
|
||||||
|
* соседа (его правая грань). */
|
||||||
|
if (code == 0x0F) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 1; }
|
||||||
|
else if (code == 0x06) { if ((pop_doorlink2(pop_tile_mod(row, col)) & 0x1F) > 1) code = 5; }
|
||||||
|
if (lcode == 0x0F) { if ((pop_doorlink2(lmod) & 0x1F) > 1) { lcode = 1; lmod = 0; } }
|
||||||
|
else if (lcode == 0x06) { if ((pop_doorlink2(lmod) & 0x1F) > 1) lcode = 5; }
|
||||||
|
int xh = POP_COL_XH[col];
|
||||||
|
int x = xh * 8;
|
||||||
|
int dby = 63 * row + 65;
|
||||||
|
int dmy = dby - 3;
|
||||||
|
const piece *t = &pop_tile_table[code];
|
||||||
|
const piece *lt = &pop_tile_table[lcode];
|
||||||
|
uint8_t rbl_code;
|
||||||
|
uint8_t wmod;
|
||||||
|
|
||||||
|
/* тайл снизу-слева (для topright). Ниже нижнего ряда — берём ряд 0
|
||||||
|
* комнаты снизу (load_rowbelow, seg008:368): row_below_left_[col] =
|
||||||
|
* room_B(0, col-1); col-1<0 → левая комната снизу = стена уровня. */
|
||||||
|
if (row + 1 > 2) {
|
||||||
|
int lc = col - 1;
|
||||||
|
/* lc < 0 — угол (2,0): сосед снизу-слева лежит в ЧУЖОЙ комнате
|
||||||
|
* (room_BL). Её тайл (0,9) приходит одиннадцатым байтом below-ряда;
|
||||||
|
* «стена» там стоит, только если такой комнаты нет — как в
|
||||||
|
* load_rowbelow (seg008:368). См. BUG-SEAM-WEDGE-1. */
|
||||||
|
if (lc < 0) rbl_code = pop_t_below ? (uint8_t)(pop_t_below[10] & 0x1F) : 20;
|
||||||
|
else rbl_code = pop_t_below ? (uint8_t)(pop_t_below[lc] & 0x1F) : 1;
|
||||||
|
} else rbl_code = pop_tile_code(row + 1, col - 1);
|
||||||
|
|
||||||
|
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
||||||
|
if (code == 0 || code == 9 || code == 12 || code == 26) {
|
||||||
|
draw_topright(rbl_code, x, dby);
|
||||||
|
/* floor-right тень (blitters_9_black) на ЧЁРНОМ фоне = no-op
|
||||||
|
* (чёрный силуэт по чёрному) — не рисуем. Если tile_left имеет
|
||||||
|
* floor_right: lt->floor_right. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- draw_tile_right (seg008.c:456) --- */
|
||||||
|
if (code != 20) {
|
||||||
|
if (lcode == 20) {
|
||||||
|
pop_wall_b(1, x, pop_tile_table[20].right_y + dmy); /* грань стены */
|
||||||
|
} else if (lcode == 0) {
|
||||||
|
if (lmod <= 3 && BLUELINE_FRAM1[lmod])
|
||||||
|
pop_env_b(BLUELINE_FRAM1[lmod], x, BLUELINE_FRAM_Y[lmod] + dmy);
|
||||||
|
} else if (lcode == 1) {
|
||||||
|
uint8_t num;
|
||||||
|
pop_env_b(42, x, pop_tile_table[1].right_y + dmy); /* правый треуг. пола */
|
||||||
|
num = (lmod <= 3) ? lmod : 0;
|
||||||
|
if (num != 0) /* dungeon: level_type=0 */
|
||||||
|
pop_env_b(BLUELINE_FRAM3[num], x, dmy - 20); /* силуэт кладки */
|
||||||
|
} else {
|
||||||
|
if (lt->right_id)
|
||||||
|
pop_env_b(lt->right_id, x, lt->right_y + dmy);
|
||||||
|
/* Основание факела (seg008:489): рисует ПРАВЫЙ сосед. */
|
||||||
|
if (lcode == 19 || lcode == 30)
|
||||||
|
pop_env_b(146, x, dby - 28);
|
||||||
|
/* stripe — только палас, dungeon пропускает */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- draw_tile_anim_right (seg008.c:530) — статичные части. Для
|
||||||
|
* верхней полосы (row<0, draw_tile_aboveroom) НЕ рисуется + защита от
|
||||||
|
* отрицательного индекса в pop_loose_modif. --- */
|
||||||
|
if (row >= 0) {
|
||||||
|
if (lcode == 4) { /* gate: решётка */
|
||||||
|
pop_gate_back_b(lmod, xh, dby, dmy);
|
||||||
|
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||||
|
pop_env_b(LOOSE_FRAM_RIGHT[pop_loose_frame(pop_loose_modif[row * 10 + (col - 1)])], x, dby - 1);
|
||||||
|
} else if (lcode == 2) { /* spike: статич. кадр */
|
||||||
|
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
||||||
|
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||||
|
pop_env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||||
|
} else if (lcode == 16) { /* дверь уровня */
|
||||||
|
draw_leveldoor(lmod, xh, dmy);
|
||||||
|
}
|
||||||
|
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
||||||
|
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
||||||
|
* нового. Рисует pop_torch_draw (динамика, pop_process_trobs). */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- draw_tile_bottom (seg008.c:570) --- */
|
||||||
|
if (code == 20) {
|
||||||
|
wmod = pop_wall_modifier(row, col);
|
||||||
|
pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); /* сплошная нижняя грань */
|
||||||
|
pop_wall_pattern_b(row, col, 0);
|
||||||
|
} else if (t->bottom_id) {
|
||||||
|
pop_env_b(t->bottom_id, x, dby);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
|
||||||
|
* верхней полосе (row<0) это ПЛИТА-ПОТОЛОК (loose ряда 2 комнаты сверху):
|
||||||
|
* её состояние живёт отдельно, в pop_ceil_modif[col] (pop_map). --- */
|
||||||
|
if (code == 11)
|
||||||
|
pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(row >= 0 ? pop_loose_modif[row * 10 + col]
|
||||||
|
: pop_ceil_modif[col])], x, dby);
|
||||||
|
|
||||||
|
/* --- draw_tile_base (seg008.c:611). Верхняя полоса (row<0) базу НЕ
|
||||||
|
* рисует (draw_tile_aboveroom) — только нижние/правые грани. --- */
|
||||||
|
if (row >= 0) {
|
||||||
|
uint8_t base_id = t->base_id;
|
||||||
|
if (code == 11) /* loose: левая грань (анимир.) */
|
||||||
|
base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])];
|
||||||
|
else if (code == 0x0F && lcode == 0) /* opener, слева пусто */
|
||||||
|
base_id = 148; /* левая половина без пола */
|
||||||
|
if (base_id)
|
||||||
|
pop_env_b(base_id, x, t->base_y + dmy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
|
||||||
|
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
|
||||||
|
* рисуется только правым соседом со сдвигом → шахта над самой пикой
|
||||||
|
* пустая. */
|
||||||
|
if (row >= 0 && code == 2) {
|
||||||
|
uint8_t sm = pop_tile_mod(row, col);
|
||||||
|
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||||||
|
if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf])
|
||||||
|
pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||||
|
}
|
||||||
|
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
||||||
|
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
||||||
|
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
||||||
|
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
||||||
|
if (row >= 0 && code == 22)
|
||||||
|
pop_pot_b((uint8_t)((pop_tile_mod(row, col) == 1) + 10), x, dmy - 3);
|
||||||
|
|
||||||
|
/* --- draw_tile_fore (seg008.c:690) --- */
|
||||||
|
if (code == 20) {
|
||||||
|
wmod = pop_wall_modifier(row, col);
|
||||||
|
pop_wall_b(POP_WALL_FRAM_MAIN[wmod], x, dmy); /* основная грань */
|
||||||
|
pop_wall_pattern_b(row, col, 1);
|
||||||
|
}
|
||||||
|
if (code == 10) { /* зелье: chtab_1, не env fore_id.
|
||||||
|
* Пузырёк — динамика (pop_potion_draw). */
|
||||||
|
pop_potion_flask(row, col, pop_tile_mod(row, col));
|
||||||
|
} else if (t->fore_id) {
|
||||||
|
pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- Loose floors — динамическая перерисовка (seg008 draw_loose) ---- *
|
||||||
|
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
|
||||||
|
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
|
||||||
|
* внутри draw_tile). */
|
||||||
|
void pop_loose_shake_draw(int row, int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
||||||
|
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
|
||||||
|
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
|
||||||
|
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
|
||||||
|
* решение (bake тайла как empty + рисовать loose всегда динамически)
|
||||||
|
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
|
||||||
|
pop_heal_off(x, 63 * row + 46, 64, 24);
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
draw_tile(row, col); /* loose: лево+низ (анимир.) */
|
||||||
|
if (col + 1 < 10)
|
||||||
|
draw_tile(row, col + 1); /* сосед: правая грань loose (анимир.) + его пол */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовать пики тайла (row,col) на back-странице по ЖИВОМУ modif
|
||||||
|
* (pop_trob). Кадр выдвижения рисует ПРАВЫЙ сосед (draw_tile ветка lcode==2
|
||||||
|
* читает lmod = pop_t_bg[row*10+col] = живой modif пики). pop_t_bg должен указывать
|
||||||
|
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
|
||||||
|
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||||||
|
void pop_spike_redraw(int row, int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
||||||
|
* redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его
|
||||||
|
* ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя —
|
||||||
|
* порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и
|
||||||
|
* fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну
|
||||||
|
* справа. Заодно draw_tile сам восстанавливает вклад соседних пик:
|
||||||
|
* в ячейке col — правое остриё пики col-1 (ветка lcode==2).
|
||||||
|
* Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх
|
||||||
|
* (63*row+34 .. +74); ширина 64 = обе ячейки. */
|
||||||
|
pop_heal_off(x, 63 * row + 34, 64, 40);
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
draw_tile(row, col);
|
||||||
|
if (col + 1 < 10)
|
||||||
|
draw_tile(row, col + 1);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
|
||||||
|
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
|
||||||
|
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = pop_t_lbg[0]).
|
||||||
|
* pop_t_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
|
||||||
|
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
|
||||||
|
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
|
||||||
|
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
|
||||||
|
void pop_room_redraw_seam_left(void) __banked
|
||||||
|
{
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
/* Полосу кладки У ПОТОЛКА (ряд −1) НЕ трогаем: её низ на room-space y=2,
|
||||||
|
* а бары ворот начинаются с y=3 (gate_top_y = dby−62). Раньше bar шёл от
|
||||||
|
* POP_YOFF и стирал потолок, из-за чего приходилось перерисовывать ещё и
|
||||||
|
* draw_tile(-1,0) — вдвое дороже на КАЖДОМ кадре анимации решётки. */
|
||||||
|
bar(0, POP_YOFF + 3, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Плита-потолок (loose ряда 2 комнаты СВЕРХУ) ------------------- *
|
||||||
|
* В полосе кладки у потолка (наш ряд −1) виден только НИЗ плиты
|
||||||
|
* (draw_tile_aboveroom: bottom+loose, всё выше отрезано клипом POP_YOFF).
|
||||||
|
* Дрожащий кадр берётся из pop_ceil_modif[col] (см. draw_loose выше).
|
||||||
|
* shake — на текущей (back) странице поверх фона; bake — «запечь» пустоту
|
||||||
|
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
|
||||||
|
|
||||||
|
void pop_ceil_shake_draw(int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
pop_heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
pop_t_clip_top = POP_YOFF;
|
||||||
|
draw_tile(-1, col);
|
||||||
|
if (col + 1 < 10) draw_tile(-1, col + 1);
|
||||||
|
pop_t_clip_top = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_ceil_bake_empty(int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(x, POP_YOFF, x + 63, POP_YOFF + CEIL_BAND_H); /* стереть плиту в ОЗУ-копии */
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
pop_t_clip_top = POP_YOFF;
|
||||||
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
|
/* Восстанавливаем ВСЁ, чья графика попадает в стёртую полосу, а не только
|
||||||
|
* два тайла ряда −1 (BUG-LOOSE-3). Куски тайла рисуются ВВЕРХ от своей
|
||||||
|
* нижней грани и свисают ВПРАВО, поэтому в полосу над тайлом col лезут:
|
||||||
|
* • ряд −1, колонки col−1..col+1 — сама полоса кладки у потолка;
|
||||||
|
* • ряд 0, колонки col−1..col+1 — их верхушки заходят в те же 8 px.
|
||||||
|
* Раньше перерисовывались только (−1,col) и (−1,col+1), и в полосе
|
||||||
|
* оставался чёрный прямоугольник ~13×3 px (замер: экранные y 47..52 при
|
||||||
|
* полосе 44..52), который heal возвращал каждый кадр. Признак, что дело
|
||||||
|
* именно в запечке: после выхода из комнаты и возврата бара нет. */
|
||||||
|
{
|
||||||
|
int c0 = (col > 0) ? col - 1 : 0;
|
||||||
|
int c1 = (col + 1 < 9) ? col + 1 : 9;
|
||||||
|
int c;
|
||||||
|
for (c = c0; c <= c1; c++) draw_tile(-1, c);
|
||||||
|
for (c = c0; c <= c1; c++) draw_tile(0, c);
|
||||||
|
}
|
||||||
|
pop_t_bake_rest = 0;
|
||||||
|
pop_t_clip_top = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Анимация факела и зелья (динамика, каждый кадр) ---------------- *
|
||||||
|
* Оба кадра рисуются ПОВЕРХ статического фона и стираются heal'ом своей
|
||||||
|
* области (фон запечён БЕЗ них). Факел: пламя chtab_1 id modifier+1 в
|
||||||
|
* ячейке ПРАВОГО соседа (seg008:560: xh = draw_xh + 1, ybottom =
|
||||||
|
* draw_main_y − 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
|
||||||
|
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y −
|
||||||
|
* (pot_size<<2) − 14 (seg008:668). */
|
||||||
|
void pop_torch_draw(int row, int col, uint8_t modif) __banked
|
||||||
|
{
|
||||||
|
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = POP_COL_XH[col+1] + 1
|
||||||
|
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
|
||||||
|
* 146), которое тоже лежит в ячейке соседа. */
|
||||||
|
int x = (col + 1 < 10 ? POP_COL_XH[col + 1] : POP_COL_XH[col] + 4) * 8 + 8;
|
||||||
|
int yb = 63 * row + 62 - 40;
|
||||||
|
if (modif >= 9) return;
|
||||||
|
/* 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);
|
||||||
|
pop_pot_b((uint8_t)(modif + 1), x, yb);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
||||||
|
{
|
||||||
|
uint8_t type = potion_type(modif);
|
||||||
|
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||||||
|
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
||||||
|
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||||||
|
if (type == 0) return;
|
||||||
|
pop_heal_off(x - 1, yb - 10, 8, 12);
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
||||||
|
pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
|
||||||
|
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
|
||||||
|
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
|
||||||
|
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
|
||||||
|
void pop_floor_bake(int row, int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
int yb = POP_YOFF + 63 * row;
|
||||||
|
/* Wipe СВОЕЙ ячейки + правой грани (заходит в соседа) + перерисовка пола.
|
||||||
|
* НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда ниже
|
||||||
|
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(x, yb + 26, x + 59, yb + 64);
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
|
draw_tile(row, col);
|
||||||
|
if (col + 1 < 10) draw_tile(row, col + 1);
|
||||||
|
pop_t_bake_rest = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
|
||||||
|
|
||||||
|
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
||||||
|
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
||||||
|
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
||||||
|
* Куски двери непрозрачные — wipe не нужен. */
|
||||||
|
void pop_leveldoor_redraw(int row, int col) __banked
|
||||||
|
{
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
draw_tile(row, col);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ворота ВНУТРИ комнаты: перерисовать бары по живому modif (порт draw_trob,
|
||||||
|
* seg007:01E6 — им заканчивается animate_door). (row,col) — сами ворота;
|
||||||
|
* решётку рисует ячейка ПРАВОГО соседа (draw_tile, ветка lcode==4), поэтому
|
||||||
|
* wipe и redraw идут по ней. Логика ровно как у левого шва
|
||||||
|
* (pop_room_redraw_seam_left), только колонка произвольная.
|
||||||
|
*
|
||||||
|
* Bar-в-чёрный обязателен: бары висят в видео-ОЗУ и ОЗУ-копии, и при
|
||||||
|
* ПОДЪЁМЕ решётки heal вернул бы запечённые нижние бары. Полоса — ровно
|
||||||
|
* зона решётки: от gate_top_y (dby−62 = 63*row+3) до низа (63*row+64);
|
||||||
|
* выше начинается ряд −1, ниже — стена ряда ниже.
|
||||||
|
*
|
||||||
|
* Ворота в col9: их бары рисуются уже в СОСЕДНЕЙ комнате, в отрисованной их
|
||||||
|
* не видно — оригинал такой trob тоже пропускает
|
||||||
|
* (get_trob_right_pos_in_drawn_room → 30). Для взгляда из комнаты справа
|
||||||
|
* работает pop_room_redraw_seam_left. */
|
||||||
|
void pop_gate_redraw(int row, int col) __banked
|
||||||
|
{
|
||||||
|
int x, yt;
|
||||||
|
if (col + 1 > 9 || row < 0 || row > 2) return;
|
||||||
|
x = POP_COL_XH[col + 1] * 8;
|
||||||
|
yt = POP_YOFF + 63 * row;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(x, yt + 3, x + 31, yt + 64);
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
draw_tile(row, col + 1);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||||||
|
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||||||
|
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||||||
|
* рисовал loose-правый-край pop_env_b(42); теперь пусто → чистая грань).
|
||||||
|
* pop_t_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
||||||
|
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
||||||
|
void pop_loose_bake_empty(int row, int col) __banked
|
||||||
|
{
|
||||||
|
int x = POP_COL_XH[col] * 8;
|
||||||
|
int xr = POP_COL_XH[col + 1] * 8;
|
||||||
|
int yb = 63 * row + 46;
|
||||||
|
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||||||
|
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
||||||
|
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
|
||||||
|
* заходит на x+32..+40. */
|
||||||
|
/* НИЗ строго 63*row+65 = низ собственной графики плиты (bottom_id 43/73/74
|
||||||
|
* стоит ровно на dby; base 41/69/70 — до dmy=62; правая грань 42/66/67 —
|
||||||
|
* до dby-1). Ниже начинается СТЕНА ряда ниже (высокий спрайт), которую
|
||||||
|
* здесь не перерисовываем: заливка на 9 строк глубже оставляла ЧЁРНЫЙ БОКС
|
||||||
|
* под соседним столбом, причём в ОЗУ-копии — то есть heal возвращал его
|
||||||
|
* снова, и на второй странице дабл-буфера его не было → мерцание.
|
||||||
|
* Тот же урок уже учтён в pop_floor_bake. */
|
||||||
|
yb += POP_YOFF;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
setfillstyle(SOLID_FILL, BLACK);
|
||||||
|
bar(x, yb, x + 39, POP_YOFF + 63 * row + 65); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||||||
|
if (col + 1 < 10)
|
||||||
|
bar(xr, yb, xr + 31, POP_YOFF + 63 * row + 65); /* сосед: РОВНО 32 — не залезть в col+2 (его не перерисовываем) */
|
||||||
|
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||||||
|
* снизу) + правый сосед (его левая грань: был loose-край pop_env_b(42),
|
||||||
|
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||||||
|
* ОЗУ-копию → heal сохранит. */
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
|
draw_tile(row, col);
|
||||||
|
if (col + 1 < 10)
|
||||||
|
draw_tile(row, col + 1);
|
||||||
|
pop_t_bake_rest = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Падающий кусок loose (mob, seg007 move_loose) ------------------ *
|
||||||
|
* Упрощ. одиночный кусок (в комнате 1 — один loose). Модель fixed-
|
||||||
|
* corridor: коридор падения (колонка тайла) heal'ится каждый кадр (стирает
|
||||||
|
* прошлый кусок, возвращает печёный фон — empty+«стена вниз»), поверх —
|
||||||
|
* кадр 0 loose (41/42/43) на текущей y. Гравитация: speed 0→+3/кадр
|
||||||
|
* (cap 29), y+=speed. Двоебуфер: коридор фиксирован → heal чистит любой
|
||||||
|
* прошлый кадр ЭТОЙ страницы. Ниже поля кусок прячет clip_borders. */
|
||||||
|
/* Падающие куски — ТАБЛИЦА (порт mobs[14]/mobs_count, seg007:965). Одного
|
||||||
|
* куска не хватает: в комнате 7 плиты (0,5) и (0,6) соседние, Kid роняет обе
|
||||||
|
* подряд, и второй спавн затирал состояние первого — его пиксели оставались
|
||||||
|
* висеть в воздухе (на каждой странице на своей y → мерцание), а щебень
|
||||||
|
* появлялся только под одной плитой. 4 слота: больше одновременно падающих
|
||||||
|
* плит в одной комнате практически не бывает (в оригинале 14). */
|
||||||
|
#define MOB_MAX 4
|
||||||
|
typedef struct {
|
||||||
|
uint8_t active; /* 0 нет; 1 падает */
|
||||||
|
uint8_t clean; /* кадров дочистки после конца полёта (2 = обе стр.) */
|
||||||
|
uint8_t defer; /* кусок ниже Kid → рисуем ПОСЛЕ него (y-порядок) */
|
||||||
|
int x; /* левый край (POP_COL_XH*8) */
|
||||||
|
int y; /* = curmob.y оригинала (низ куска, dby), растёт вниз */
|
||||||
|
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
|
||||||
|
int8_t speed;
|
||||||
|
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
|
||||||
|
uint8_t room; /* curmob.room: кусок падает В СВОЕЙ комнате, не в текущей */
|
||||||
|
} mob_t;
|
||||||
|
static mob_t mobs[MOB_MAX];
|
||||||
|
|
||||||
|
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
|
||||||
|
* который он лёг → приложение/pop_map ставит debris + do_knock. */
|
||||||
|
/* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
|
||||||
|
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
|
||||||
|
|
||||||
|
/* y_loose_land / y_something (seg007): [row+1], row = −1..3. Ряд −1 —
|
||||||
|
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
|
||||||
|
* tilepos, у нас — свой ряд полосы у потолка. */
|
||||||
|
static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
|
||||||
|
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
|
||||||
|
#define MOB_Y_NONE 0x7FFF
|
||||||
|
|
||||||
|
void pop_loose_mob_spawn(int row, int col) __banked
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
mob_t *m = 0;
|
||||||
|
/* свободный слот = не летит И дочистка завершена (иначе затрём хвост,
|
||||||
|
* который ещё не стёрт со второй страницы) */
|
||||||
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
|
if (!mobs[i].active && !mobs[i].clean) { m = &mobs[i]; break; }
|
||||||
|
if (!m) return; /* таблица полна — кусок без спрайта (тайл всё равно пуст) */
|
||||||
|
m->x = POP_COL_XH[col] * 8;
|
||||||
|
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
|
||||||
|
m->row = (int8_t)row;
|
||||||
|
m->col = (int8_t)col;
|
||||||
|
m->room = pop_t_room; /* curmob.room (add_mob, seg007:1010) */
|
||||||
|
m->speed = 0;
|
||||||
|
m->active = 1;
|
||||||
|
m->clean = 0;
|
||||||
|
m->defer = 0;
|
||||||
|
m->prev_y[0] = m->prev_y[1] = MOB_Y_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Габарит куска по X: left/bottom (41/43, w=32) с mob_x; right (42, w=26)
|
||||||
|
* с mob_x+32 → ПРАВЫЙ пиксель = mob_x+57 (замерено в pop_env1.atl, НЕ +46:
|
||||||
|
* ширина 42 = 26, не ~14). Коридор heal и клип ОБЯЗАНЫ покрыть ВСЮ ширину
|
||||||
|
* [mob_x-4 .. mob_x+57], иначе правая грань 42 остаётся хвостом на одной
|
||||||
|
* странице → тёмная тень на полу 2,7 + двоебуфер-мерцание. */
|
||||||
|
#define MOB_X0(mx) ((mx) - 4)
|
||||||
|
#define MOB_W 64 /* mob_x-4 .. mob_x+59 (крышка +57 с запасом 2px) */
|
||||||
|
|
||||||
|
/* Остановить падающий кусок (при смене комнаты — состояние per-tile не
|
||||||
|
* должно утечь в новую комнату). */
|
||||||
|
static void mob_render(mob_t *m, uint8_t pg);
|
||||||
|
|
||||||
|
void pop_loose_mob_reset(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < MOB_MAX; i++) {
|
||||||
|
mobs[i].active = 0; mobs[i].clean = 0; mobs[i].defer = 0;
|
||||||
|
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Смена комнаты. Куски НЕ останавливаем: в оригинале mobs[] чистится только
|
||||||
|
* в start_level (seg003:88), а do_mobs (seg007:1063) крутит их все независимо
|
||||||
|
* от drawn_room — move_loose работает по curmob.room, и плита, за которой Кид
|
||||||
|
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
|
||||||
|
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
|
||||||
|
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
|
||||||
|
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;
|
||||||
|
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
|
||||||
|
if (!mobs[i].active) mobs[i].clean = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
|
||||||
|
* и соседей), в чужой — прямо из данных уровня. */
|
||||||
|
static uint8_t mob_tile_at(mob_t *m)
|
||||||
|
{
|
||||||
|
if (m->room == pop_t_room) return (uint8_t)(pop_tile_code(m->row, m->col) & 0x1F);
|
||||||
|
if (m->row < 0 || m->row > 2 || m->col < 0 || m->col > 9) return 1; /* кромка = пол */
|
||||||
|
return (uint8_t)(pop_level_tile(m->room, (uint8_t)(m->row * 10 + m->col)) & 0x1F);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mob_tick_one(mob_t *m, uint8_t pg)
|
||||||
|
{
|
||||||
|
uint8_t here;
|
||||||
|
if (!m->active && !m->clean) return;
|
||||||
|
here = (uint8_t)(m->room == pop_t_room);
|
||||||
|
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
|
||||||
|
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
|
||||||
|
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
|
||||||
|
* прошлой позиции. */
|
||||||
|
if (here && m->prev_y[pg] != MOB_Y_NONE) {
|
||||||
|
pop_heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
|
||||||
|
m->prev_y[pg] = MOB_Y_NONE;
|
||||||
|
}
|
||||||
|
if (!m->active) { m->clean--; return; }
|
||||||
|
/* гравитация (move_loose): ускорение до 29 */
|
||||||
|
if (m->speed < 29) m->speed += 3;
|
||||||
|
m->y += m->speed;
|
||||||
|
/* move_loose (seg007:1126): низ куска пересёк границу своего ряда —
|
||||||
|
* смотрим тайл этого ряда. Пусто/loose — валится дальше (порт
|
||||||
|
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
|
||||||
|
* пол — приземление (loose_land: debris + do_knock). */
|
||||||
|
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
|
||||||
|
uint8_t t = mob_tile_at(m);
|
||||||
|
if (t == 0 || t == 11) {
|
||||||
|
m->row++; /* mob_down_a_row */
|
||||||
|
} else if (!here) {
|
||||||
|
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, сигнальная
|
||||||
|
* пара pop_loose_landed/pop_debris_at обслуживает только текущую
|
||||||
|
* комнату — щебень кладём прямо в данные уровня (loose_land пишет
|
||||||
|
* в curr_room_tiles комнаты curmob.room, а не отрисованной). */
|
||||||
|
pop_level_set_tile(m->room, (uint8_t)(m->row * 10 + m->col), 0x0E);
|
||||||
|
m->active = 0; m->clean = 0;
|
||||||
|
return;
|
||||||
|
} else if (pop_loose_landed) {
|
||||||
|
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
|
||||||
|
* подержим кусок на границе и приземлим следующим кадром */
|
||||||
|
m->y = MOB_Y_BOUND[m->row + 1];
|
||||||
|
} else {
|
||||||
|
m->y = MOB_Y_BOUND[m->row + 1];
|
||||||
|
pop_loose_landed = (uint8_t)(m->row * 10 + m->col + 1);
|
||||||
|
m->active = 0; m->clean = 2; /* кусок исчез — на его месте debris */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
|
||||||
|
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!here) return; /* чужая комната — считаем, но не рисуем */
|
||||||
|
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
|
||||||
|
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
|
||||||
|
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
|
||||||
|
* ниже — тот позже, т.е. поверх). Тайл падающего куска — draw_mob
|
||||||
|
* (seg007:13E5): row = y_to_row_mod4(y), col = xh>>2. Значит кусок
|
||||||
|
* поверх Kid, если его тайл идёт в обходе ПОЗЖЕ тайла Kid, а при
|
||||||
|
* совпадении тайлов — если он ниже по y. */
|
||||||
|
{
|
||||||
|
int8_t mrow = (int8_t)(((m->y + 60) / 63) % 4 - 1); /* y_to_row_mod4 */
|
||||||
|
uint8_t over;
|
||||||
|
if (mrow != pop_bg_obj_row) over = (mrow < pop_bg_obj_row); /* ряды идут 2,1,0 */
|
||||||
|
else if (m->col != pop_bg_obj_col) over = (m->col > pop_bg_obj_col);
|
||||||
|
else over = (m->y > pop_cd[POP_CH_KID].fpy);
|
||||||
|
if (over) { m->defer = 1; return; }
|
||||||
|
}
|
||||||
|
mob_render(m, pg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_loose_mob_tick(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||||||
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
|
mob_tick_one(&mobs[i], pg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mob_render(mob_t *m, uint8_t pg)
|
||||||
|
{
|
||||||
|
/* Падающий кусок (SDLPoP draw_mobs/add_midtable, case 0x80): ТРИ куска —
|
||||||
|
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
|
||||||
|
* заходит в тайл СПРАВА) на obj_y-1. */
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
pop_env_b(43, m->x, m->y);
|
||||||
|
pop_env_b(41, m->x, m->y - 3);
|
||||||
|
pop_env_b(42, m->x + 32, m->y - 1);
|
||||||
|
m->prev_y[pg] = m->y;
|
||||||
|
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
|
||||||
|
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
|
||||||
|
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
|
||||||
|
* её правый край ПРЯЧЕТСЯ за передней гранью пола. Перерисовываем
|
||||||
|
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
|
||||||
|
* до heal'а следующего кадра — виден как хвост во время падения). */
|
||||||
|
if (m->col + 1 < 10) {
|
||||||
|
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
|
||||||
|
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
|
||||||
|
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
|
||||||
|
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
|
||||||
|
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
|
||||||
|
* через пол + перекрыта передняя грань пола.
|
||||||
|
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
|
||||||
|
* границе рядов. Порт draw_mob (seg007:13E5). */
|
||||||
|
int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1);
|
||||||
|
int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1);
|
||||||
|
if (r < 0) pop_t_clip_top = POP_YOFF; /* полоса у потолка — только срез */
|
||||||
|
draw_tile(r, m->col + 1);
|
||||||
|
pop_t_clip_top = 0;
|
||||||
|
if (rt != r) {
|
||||||
|
if (rt < 0) pop_t_clip_top = POP_YOFF;
|
||||||
|
draw_tile(rt, m->col + 1);
|
||||||
|
pop_t_clip_top = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
|
||||||
|
pop_clip_sprite(MOB_X0(m->x), MOB_W, m->y - 26 + POP_YOFF, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
|
||||||
|
* после отрисовки персонажей, до fore-слоя. */
|
||||||
|
void pop_loose_mob_draw_over(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t pg = gfx_get_draw_page() & 1, i;
|
||||||
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
|
if (mobs[i].defer) { mobs[i].defer = 0; mob_render(&mobs[i], pg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
|
||||||
|
* y (низ куска). 0 — куска нет. */
|
||||||
|
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
/* check_loose_fall_on_kid (seg007:1192) начинается с Char.room ==
|
||||||
|
* curmob.room: кусок, падающий в покинутой комнате, Киду по голове не
|
||||||
|
* бьёт. */
|
||||||
|
for (i = 0; i < MOB_MAX; i++)
|
||||||
|
if (mobs[i].active && mobs[i].room == pop_t_room) {
|
||||||
|
*col = mobs[i].col; *y = mobs[i].y; return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Спрайт вышел за поле? → взвести флаг на 2 кадра (обе страницы дабл-
|
||||||
|
* буфера). Узкий per-спрайт heal НЕ используем: accel-выравнивание блока
|
||||||
|
* оставляет 1–2px слайверы правой грани → на одной странице мерцает.
|
||||||
|
* Вместо этого борта чистит pop_room_clip_borders (полоса, но ТОЛЬКО в
|
||||||
|
* кадрах падения — гейт по флагу). */
|
||||||
|
static uint8_t border_dirty;
|
||||||
|
|
||||||
|
void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
|
||||||
|
{
|
||||||
|
int sy_bot = sy_top + h;
|
||||||
|
(void)x; (void)w;
|
||||||
|
if (sy_bot > POP_CLIP_BOTTOM || sy_top < POP_YOFF)
|
||||||
|
border_dirty = 2; /* 2 кадра = обе страницы */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Клип бортов (см. pop_bg.h): работает ТОЛЬКО пока border_dirty (кто-то
|
||||||
|
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
|
||||||
|
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
|
||||||
|
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
|
||||||
|
void pop_room_clip_borders(void) __banked
|
||||||
|
{
|
||||||
|
if (!border_dirty) return;
|
||||||
|
border_dirty--;
|
||||||
|
/* ОСТАЁТСЯ клипающим осознанно (CLIP-1, 2026-08-01): полоса шириной 320
|
||||||
|
* не лезет в 8-битный параметр noclip-ядра, а разбивать её на два куска
|
||||||
|
* по 160 нет смысла — гейт border_dirty пускает сюда только в кадрах
|
||||||
|
* падения/выхода за поле, и выигрыш (~2×5.6 К тактов подготовки) там
|
||||||
|
* тонет в цене самих 320×28 пикселей. Не «оптимизировать» повторно. */
|
||||||
|
gfx_heal(0, 0, 320, POP_YOFF); /* верхний борт */
|
||||||
|
gfx_heal(0, POP_CLIP_BOTTOM, 320, 256 - POP_CLIP_BOTTOM);/* статус-полоса + нижний борт */
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_room_draw(uint8_t room_num,
|
||||||
|
const uint8_t *fg, const uint8_t *bg,
|
||||||
|
const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
|
||||||
|
{
|
||||||
|
int row, col;
|
||||||
|
pop_t_room = room_num;
|
||||||
|
pop_wall_pattern_reset_b(); /* кэш кладки привязан к номеру комнаты */
|
||||||
|
pop_t_fg = fg; pop_t_bg = bg;
|
||||||
|
pop_t_lfg = lcol_fg; pop_t_lbg = lcol_bg;
|
||||||
|
|
||||||
|
/* прозрачный блит: 0xFF не пишется (чёрный фон/нижние слои проступают) */
|
||||||
|
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||||
|
pop_t_bake_rest = 1; /* фон = покой (см. pop_t_bake_rest) */
|
||||||
|
for (row = 2; row >= 0; row--) /* снизу вверх (2,1,0) */
|
||||||
|
for (col = 0; col < 10; col++)
|
||||||
|
draw_tile(row, col);
|
||||||
|
/* Полоса кладки у потолка (seg008 draw_room: доп. ряд из room_A, row2
|
||||||
|
* при draw_main_y=-1): нижние/правые грани ряда 2 комнаты сверху. Кадры
|
||||||
|
* высокие — верх ушёл бы в борт (за поле); клип по верхней границе поля
|
||||||
|
* (POP_YOFF) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
|
||||||
|
pop_t_clip_top = POP_YOFF;
|
||||||
|
for (col = 0; col < 10; col++)
|
||||||
|
draw_tile(-1, col);
|
||||||
|
pop_t_clip_top = 0;
|
||||||
|
pop_t_bake_rest = 0;
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
}
|
||||||
@@ -50,19 +50,21 @@ __sfr __at (0xFE) io_border;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||||
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
* 1 = guards (ИИ/боёвка), 2 = pop_bg (ГОРЯЧАЯ половина фона: fore-проход,
|
||||||
* (коллизия/физика), 4 = pop_cdraw (отрисовка персонажей + полоса HP),
|
* оверлеи, кладка), 3 = pop_map (коллизия/физика), 4 = pop_cdraw
|
||||||
* 5 = pop_ctrl (диспетчер управления).
|
* (отрисовка персонажей + полоса HP), 5 = pop_ctrl (диспетчер управления),
|
||||||
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
* 6 = pop_trob (ловушки/анимация тайлов), 7 = pop_room (ХОЛОДНАЯ половина
|
||||||
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
* фона: полная отрисовка комнаты, точечные перерисовки, падающие плиты).
|
||||||
* уровней, чомперы, зеркало.
|
* Отрисовка персонажей и холодная половина фона вынесены из банка 2
|
||||||
|
* отдельно: тот дважды подходил к 16 КБ вплотную, а расти ему ещё есть
|
||||||
|
* куда — тайлы поздних уровней, чомперы, зеркало.
|
||||||
*
|
*
|
||||||
* ДЕРЖАТЬ В СОГЛАСИИ с числом --bank в Makefile. Забыть — это не ошибка
|
* ДЕРЖАТЬ В СОГЛАСИИ с числом --bank в Makefile. Забыть — это не ошибка
|
||||||
* сборки, а зависание: crt0 выделит меньше страниц, _bank_pages[N] так и
|
* сборки, а зависание: crt0 выделит меньше страниц, _bank_pages[N] так и
|
||||||
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
* останется нулём, и трамплин прыгнет по 0xC000 в мусор (см. шапку
|
||||||
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
* runtime/bank.s). Проверено 2026-08-05: с n_banks=4 и пятым банком игра
|
||||||
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
* доходила до конца загрузки ресурсов и вставала намертво в DSS. */
|
||||||
const uint8_t n_banks = 6;
|
const uint8_t n_banks = 7;
|
||||||
|
|
||||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||||
uint8_t pop_cheats;
|
uint8_t pop_cheats;
|
||||||
|
|||||||
Reference in New Issue
Block a user