roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME: - Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57) теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный хвост-тень на полу 2,7 ПОСЛЕ падения. - Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1). - Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq -> флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80). KNOCK_DOWN в land-seq (приземление) и runcyc (footstep). - Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0) перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) — иначе на одной странице застревает дрожащий кадр правой грани (живёт в тайлах col и col+1) -> мерцание через флип. Инфраструктура/документация: - app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME). - docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT. - README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP — источник истины», порядок слоёв, режим отладки freeze 1/2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Prince of Persia → ZX Sprinter — правила подпроекта
|
||||
|
||||
Порт Prince of Persia (DOS/Apple II) на Sprinter Sp2000 поверх нашего
|
||||
sprinter-cc / libc / libbgi. Действуют правила корневого
|
||||
`CLAUDE.md` (сборка, libc, ABI, MAME-автотест); ниже — только специфика PoP.
|
||||
Общение и комментарии — на русском.
|
||||
|
||||
## Главное правило: SDLPoP — источник истины. Сначала читай, потом кодь
|
||||
|
||||
**`SDLPoP/src/` (github.com/NagyD/SDLPoP, GPLv3) — ЕДИНСТВЕННЫЙ авторитетный
|
||||
источник того, как оригинальный движок это делает.** Правило без исключений:
|
||||
|
||||
1. **Перед реализацией ЛЮБОЙ функции** (движение, коллизия, окклюзия,
|
||||
падение, loose-полы, стражники, отрисовка, тайминги, любые числовые
|
||||
константы) — СНАЧАЛА найди и прочитай соответствующий код в `SDLPoP/src/`,
|
||||
и портируй по нему. Не пиши по памяти, не выводи логику «из общих
|
||||
соображений», не угадывай значения — это источник багов, которые потом
|
||||
ловятся в MAME часами.
|
||||
2. **По любому вопросу «как в оригинале должно быть»** (что окклюдит что,
|
||||
в каком порядке слои, когда меняется тайл, какая скорость/задержка,
|
||||
что делает такой-то кадр анимации) — ответ ищи в `SDLPoP/src/`, а не
|
||||
строй гипотезу. Если в SDLPoP не нашёл — это повод копать дальше в
|
||||
исходнике, а не додумывать.
|
||||
3. Расхождение нашей реализации с SDLPoP — по умолчанию **баг у нас**, пока
|
||||
не доказано обратное (наша платформа/ABI требует отличия — тогда явно
|
||||
зафиксировать почему в комментарии).
|
||||
|
||||
Карта сегментов: `seg005` control-диспетчер, `seg006` play_kid/коллизия/
|
||||
seqtbl, `seg007` mob/loose/падающие объекты, `seg008` отрисовка тайлов/
|
||||
слои/окклюзия, `seg009` чтение ресурсов. Слои окклюзии у нас = слои SDLPoP.
|
||||
См. memory `pop_check_sdlpop_first`.
|
||||
|
||||
Вторичные референсы (когда в SDLPoP непонятно/нужен другой ракурс):
|
||||
- `Prince-of-Persia-Apple-II/` — оригинальный 6502-исходник 1989 (Мехнер).
|
||||
- `PR/` (github.com/NagyD/PR, GPLv2) — Princed Resources.
|
||||
- `mininim/` — независимая реализация.
|
||||
|
||||
Все эти папки — **справочник логики/структур/констант и источник ассетов**,
|
||||
но НЕ код для копирования (лицензии несовместимы, наш ABI другой): читаем
|
||||
и переписываем под наш движок, а не вставляем куски.
|
||||
|
||||
## Ассеты
|
||||
|
||||
Готовые распакованные VGA-256 ассеты (то, что нужно под 320×256×256) —
|
||||
`SDLPoP/data/` (`res<id>.png`/`.pal`/`.bin`). Брать оттуда, а НЕ писать свой
|
||||
декодер DOS `.DAT`. Локальные `.DAT` — в `MSDOS/`.
|
||||
|
||||
**Каноническая спецификация форматов `.DAT` — `docs/POP-DAT-FormatSpecifications.pdf`**
|
||||
(грепаемая копия — `docs/POP-DAT-FormatSpecifications.txt`): первоисточник
|
||||
Princed для DAT v1.0 (контейнер/индекс/чек-сумма, кодеки RLE/LZG, палитры,
|
||||
формат уровней, звук), на нём построены и SDLPoP, и Princed Resources. Наши
|
||||
разборы (`docs/MSDOS_RESOURCE_FORMAT.md` / `docs/APPLEII_RESOURCE_FORMAT.md` /
|
||||
`docs/README.md`) — практические заметки/сверки; при расхождении источник
|
||||
истины — спецификация. Формат уровня почти идентичен в Apple II и DOS.
|
||||
|
||||
Упаковка ассетов под Sprinter (атласы `.atl`, палитра) — python-скрипты в
|
||||
`toolchain/` (`render_room.py`, `pop_pack_bg.py`, `pop_pack_kid.py`,
|
||||
`pop_extract_kid_data.py`). Их дёргают Makefile'ы тестов.
|
||||
|
||||
## Структура папки
|
||||
|
||||
- `docs/` — планы и форматы: `PORT_PLAN.md` (общий план фаз),
|
||||
`KID_PLAN.md`, `double_buffer_plan.md`, `loose_floors_plan.md`, форматы
|
||||
ресурсов. Начинать чтение отсюда.
|
||||
- `roomtest/` — **активная разработка**: комната 1 + Kid (анимация, ввод,
|
||||
коллизия, падение, зацеп, fore-окклюзия, loose-полы). Свой `CLAUDE.md`.
|
||||
- `poc/` — ранний proof-of-concept (снег/атлас/kbd_raw); ассеты в `poc/res/`.
|
||||
- `bgtest/`, `coltest/` — отдельные проверки фона/коллизии.
|
||||
- `toolchain/` — python-упаковщики ассетов + эталонные PNG (`1.1-2.png`).
|
||||
- `SDLPoP/`, `PR/`, `Prince-of-Persia-Apple-II/`, `mininim/`, `MSDOS/` —
|
||||
референсы/оригинальные данные (см. выше).
|
||||
|
||||
## Ключевые архитектурные решения (memory/)
|
||||
|
||||
- `pop_port_project` — общий статус порта.
|
||||
- `pop_banking_architecture` — будущее: big+BANK_W1, графику нельзя в W3,
|
||||
один файл = один банк = прямые вызовы, main резидентен.
|
||||
- `pop_background_strategy` — фон = композиция тайлов в рантайме (вариант 3).
|
||||
- `pop_kid_plan` / `pop_hang_state` / `pop_fore_layer` /
|
||||
`pop_fall_debug_baseline` — этапы Kid.
|
||||
- `kbd_raw_fifo_drain` — held-state клавиатуры (единственный принципиальный
|
||||
пробел движка, закрыт `<kbd_raw.h>`): вычерпывать FIFO SIO циклом.
|
||||
- `png_strip_padding_tradeoff`, `pop_tile_atlas_palette_merge` — квирки
|
||||
упаковки ассетов.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Prince of Persia на ZX Sprinter
|
||||
|
||||
Порт Prince of Persia на компьютер Sprinter Sp2000 поверх нашего
|
||||
target-слоя SDCC (sprinter-cc / libc / libbgi). Цель — 320×256×256 (режим
|
||||
0x81), VGA-256 ассеты оригинала переносятся почти впрямую.
|
||||
|
||||
Общий план и статус фаз — [`docs/PORT_PLAN.md`](docs/PORT_PLAN.md).
|
||||
Правила работы для ИИ-сессий — [`CLAUDE.md`](CLAUDE.md).
|
||||
|
||||
## Что где
|
||||
|
||||
| Папка | Назначение |
|
||||
|-------|-----------|
|
||||
| `roomtest/` | **Активная разработка.** Комната 1 уровня 1 живой композицией тайлов + Kid: анимация (seqtbl), управление с клавиатуры, коллизия, падение, зацеп/подтягивание, fore-окклюзия, проваливающиеся полы. Свой README/CLAUDE. |
|
||||
| `docs/` | Планы (`PORT_PLAN`, `KID_PLAN`, `double_buffer_plan`, `loose_floors_plan`) и разбор форматов ресурсов Apple II / DOS. |
|
||||
| `toolchain/` | Python-упаковщики ассетов под Sprinter (рендер комнат, атласы тайлов/Kid, извлечение данных анимации) + эталонные скриншоты. |
|
||||
| `poc/` | Ранний proof-of-concept (снег, атлас, raw-клавиатура). Ассеты в `poc/res/`. |
|
||||
| `bgtest/`, `coltest/` | Точечные проверки фона и коллизии. |
|
||||
| `SDLPoP/`, `PR/`, `mininim/` | Референсные реализации движка (GPL) — читаем логику/константы, НЕ копируем код. `SDLPoP/data/` — источник распакованных VGA-ассетов. |
|
||||
| `Prince-of-Persia-Apple-II/` | Оригинальный 6502-исходник 1989 г. |
|
||||
| `MSDOS/` | Локальные `.DAT`-ресурсы DOS-версии. |
|
||||
|
||||
## Референсы = только справочник
|
||||
|
||||
`SDLPoP/`, `PR/`, `mininim/`, `Prince-of-Persia-Apple-II/` используются как
|
||||
справочник структур/логики и как источник готовых ассетов — их код НЕ
|
||||
копируется в наш порт (лицензии несовместимы, ABI другой). Любая механика
|
||||
сверяется с `SDLPoP/src/` **до** реализации.
|
||||
|
||||
## Форматы ресурсов
|
||||
|
||||
Формат уровня почти идентичен в Apple II и DOS (2304 / 2305 байт,
|
||||
`blueprnt`). Графика различается принципиально, но брать распакованные PNG
|
||||
из `SDLPoP/data/` практичнее, чем декодировать сырой `.DAT`. Подробности —
|
||||
[`docs/README.md`](docs/README.md).
|
||||
@@ -8,8 +8,18 @@
|
||||
(например: смещение+размер последней записи таблицы точно совпадает с
|
||||
началом самой таблицы — то есть данные и каталог стыкуются без дыр).
|
||||
|
||||
Для справки при последующей реализации (порт на ZX Sprinter) стоит держать в
|
||||
уме два внешних проекта:
|
||||
**Основной источник спецификации формата — `POP-DAT-FormatSpecifications.pdf`**
|
||||
(и его текстовая конверсия `POP-DAT-FormatSpecifications.txt` в этой же папке,
|
||||
для grep/цитирования): *«Prince of Persia — Specifications of File Formats»*,
|
||||
Princed Development Team, 2008 — каноническая спецификация формата `DAT v1.0`,
|
||||
на которой построен и SDLPoP, и Princed Resources. Разбирает контейнер, индекс,
|
||||
чек-сумму, кодеки изображений (RLE / LZG), палитры, формат уровней (room
|
||||
mapping, wall-drawing, room-linking, guards, start position, door events),
|
||||
звук (digital waves / MIDI / PC speaker), бинарные файлы и Mac-варианты. При
|
||||
любом расхождении между эмпирическими находками ниже и этим документом —
|
||||
источником истины считать спецификацию (сверять §-номера: её §3.x).
|
||||
|
||||
Дополнительно как справка при реализации (порт на ZX Sprinter):
|
||||
|
||||
- **SDLPoP** (github.com/NagyD/SDLPoP, GPLv3) — open-source реализация
|
||||
DOS-версии на основе дизассемблирования оригинального `PRINCE.EXE`. Содержит
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,13 @@
|
||||
# Форматы ресурсов Prince of Persia — сводка
|
||||
|
||||
**Каноническая спецификация форматов** — `POP-DAT-FormatSpecifications.pdf`
|
||||
(+ текстовая конверсия `POP-DAT-FormatSpecifications.txt` для grep/цитирования):
|
||||
*«Prince of Persia — Specifications of File Formats»*, Princed Development Team,
|
||||
2008. Это первоисточник формата `DAT v1.0` (контейнер, индекс, чек-сумма,
|
||||
кодеки RLE/LZG, палитры, уровни, звук), на котором построены и SDLPoP, и
|
||||
Princed Resources. Документы ниже — наши практические заметки/сверки; при
|
||||
расхождении источником истины считать спецификацию.
|
||||
|
||||
Цель этих документов — подготовить почву для будущего порта Prince of Persia
|
||||
на ZX Sprinter, разобрав, как устроены ресурсы игры в двух доступных нам
|
||||
версиях:
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
# roomtest — правила подпроекта
|
||||
|
||||
Живой прототип порта PoP: комната 1 уровня 1 (композиция тайлов в рантайме)
|
||||
+ Kid (анимация seqtbl, управление, коллизия, падение, зацеп, fore-окклюзия,
|
||||
проваливающиеся полы). Действуют правила корневого `CLAUDE.md` и
|
||||
`applications/PoP/CLAUDE.md`.
|
||||
|
||||
**Главное правило (из `../CLAUDE.md`): `../SDLPoP/src/` — источник истины.**
|
||||
Перед реализацией ЛЮБОЙ функции и по ЛЮБОМУ вопросу «как в оригинале» —
|
||||
СНАЧАЛА прочитай соответствующий код SDLPoP и портируй по нему; не пиши
|
||||
по памяти и не угадывай константы/порядок слоёв. Расхождение с SDLPoP =
|
||||
по умолчанию баг у нас.
|
||||
|
||||
## Сборка и запуск
|
||||
|
||||
```
|
||||
make # собрать roomtest.exe (упаковав ассеты через toolchain/)
|
||||
make run # exe + EXTRA_DATA на дискету + запуск MAME (см. корневой док)
|
||||
```
|
||||
|
||||
`MEMORY=small`, `--gfx 256`. Ассеты (`pop_env0..4.atl`, `pop_wall.atl`,
|
||||
`pop_fore.atl`, `kid0..27.atl`, `kid.pal`) генерируются python-скриптами
|
||||
`../toolchain/` — Makefile дёргает их сам при изменении. Данные комнаты —
|
||||
`room1_data.h` (fg=foretable code, bg=backtable modifier); данные анимации
|
||||
Kid — `kid_data.h` (генерится `pop_extract_kid_data.py`).
|
||||
|
||||
## Проверка в MAME
|
||||
|
||||
Только через `toolchain/mame_interactive.py` или MCP-мост `mame-z80`
|
||||
(см. memory `mame_autotest`, `mame_mcp_bridge`, `mame_hdd_test_disk`).
|
||||
Эталон комнаты — `../toolchain/1.1-2.png`. Пересобрал HDD-образ → MAME
|
||||
ОБЯЗАН полный рестарт (`mame_hdd_rebuild_restart`).
|
||||
|
||||
Отладочные тумблеры в живой сессии (`roomtest.c`): **SPACE** — вкл/выкл
|
||||
дабл-буфер (в однобуфере видно баги рисования без мерцания-через-кадр);
|
||||
**1** — заморозить кадр, **2** — продолжить (разбор позы/окклюзии);
|
||||
**ESC** — выход.
|
||||
|
||||
## Модули
|
||||
|
||||
| Файл | Роль (порт SDLPoP) |
|
||||
|------|--------------------|
|
||||
| `roomtest.c` | Главный цикл: дабл-буфер (2 страницы + флип на vsync), tick→draw порядок слоёв. |
|
||||
| `pop_bg.c/.h` | Отрисовка фона композицией тайлов (`seg008` draw_tile/wall_pattern), fore-окклюзия поверх Kid (`seg003` redraw_at_char), loose-полы (shake/bake/mob, `seg007/008`). |
|
||||
| `pop_kid.c/.h` | Анимация/движение Kid: интерпретатор seqtbl `play_seq` + frame_table (`seg006`). |
|
||||
| `pop_ctrl.c/.h` | Управление: диспетчер `control()` (`seg005`) + ввод `read_user_control` (`seg006`) поверх `<kbd_raw.h>`. |
|
||||
| `pop_map.c/.h` | Коллизия с картой + физика падения/приземления/стены + loose-полы (`seg005/006/007`). |
|
||||
|
||||
## Порядок слоёв в кадре (важно для окклюзии)
|
||||
|
||||
`roomtest.c` каждый тик рисует в СКРЫТУЮ страницу: `kid_heal` (стереть
|
||||
прошлый кадр) → `kid_tick` → `pop_phys_tick` → `pop_loose_tick` (loose СЛОЙ
|
||||
ФОНА — ДО Kid, чтобы Kid был поверх плиты) → `kid_draw` → `pop_fore_over_kid`
|
||||
(передние грани тайлов поверх Kid) → `pop_room_clip_borders` (чистка бортов,
|
||||
гейт по флагу — только в кадрах падения) → флип на vsync.
|
||||
|
||||
**Дабл-буфер:** у каждой страницы своя видео-ОЗУ и теневая ОЗУ-копия; heal
|
||||
берёт чистый фон из копии ТОЙ страницы, в которую рисуем. Любой динамический
|
||||
элемент (Kid, loose-плита, падающий кусок) обязан чистить свой прошлый кадр
|
||||
на КАЖДОЙ из двух страниц — иначе остаток виден через кадр как **мерцание**.
|
||||
Это типовой источник багов «остаётся кусочек» (см. memory
|
||||
`pop_fall_debug_baseline`).
|
||||
|
||||
## Ключевые memory
|
||||
|
||||
`pop_kid_plan`, `pop_hang_state`, `pop_fore_layer`, `pop_fall_debug_baseline`,
|
||||
`accel_vertical_copy`, `kbd_raw_fifo_drain`, `pop_check_sdlpop_first`.
|
||||
@@ -0,0 +1,47 @@
|
||||
# roomtest
|
||||
|
||||
Живой прототип порта Prince of Persia: комната 1 уровня 1 отрисовывается
|
||||
реальной композицией слоёв-тайлов, по ней бегает и прыгает Kid под
|
||||
управлением с клавиатуры. Служит стендом для переноса механик движка
|
||||
(анимация, коллизия, падение, зацеп, окклюзия, проваливающиеся полы) до их
|
||||
выделения в переиспользуемый код.
|
||||
|
||||
Правила разработки и порядок слоёв рендера — [`CLAUDE.md`](CLAUDE.md).
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```
|
||||
make # собрать roomtest.exe (ассеты упакуются автоматически)
|
||||
make run # собрать, упаковать на дискету и запустить в MAME
|
||||
```
|
||||
|
||||
Клавиши в запущенном тесте: ←/→ бег, ↓ присед, ↑+→ прыжок, Shift шаг,
|
||||
ESC выход.
|
||||
|
||||
**Отладка рисования:**
|
||||
- **SPACE** — тумблер дабл-буфера (в однобуфере баги рисования видны без
|
||||
мерцания-через-кадр).
|
||||
- **1** — заморозить кадр (`frozen`: tick/draw/flip пропускаются, картинка
|
||||
стоит), **2** — снять заморозку и продолжить. Приём для разбора
|
||||
транзиентных артефактов (тряска/падение loose, поза Kid): поймать
|
||||
проблемный кадр на `1`, спокойно рассмотреть/снять пиксели — в т.ч.
|
||||
удалённо из MAME MCP-моста, пока анимация стоит.
|
||||
|
||||
Эталон вида комнаты — `../toolchain/1.1-2.png`.
|
||||
|
||||
## Модули
|
||||
|
||||
- `roomtest.c` — главный цикл (дабл-буфер, порядок tick/draw).
|
||||
- `pop_bg.c` — фон комнаты композицией тайлов + fore-окклюзия + loose-полы.
|
||||
- `pop_kid.c` — анимация/движение Kid (интерпретатор seqtbl).
|
||||
- `pop_ctrl.c` — управление с клавиатуры (диспетчер control).
|
||||
- `pop_map.c` — коллизия с картой, физика падения, loose-полы.
|
||||
- `room1_data.h` — карта комнаты 1; `kid_data.h` — данные анимации Kid
|
||||
(оба генерируются скриптами `../toolchain/`).
|
||||
|
||||
## Статус
|
||||
|
||||
Готово и проверено в MAME: статический фон комнаты 1; Kid — анимация,
|
||||
управление, коллизия/падение, зацеп/подтягивание/спуск, fore-окклюзия
|
||||
пола/стены над Kid. В работе: проваливающиеся полы (loose floors) — см.
|
||||
`../docs/loose_floors_plan.md`.
|
||||
@@ -57,10 +57,13 @@ static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
if (w && h)
|
||||
gfx_blit(x, ybottom - (int)h + 1, img);
|
||||
gfx_blit(x, ybottom - (int)h + 1 + POP_YOFF, img); /* +YOFF: центрирование */
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
/* heal в room-space (со сдвигом POP_YOFF) — все heal слоя фона/loose. */
|
||||
static void heal_off(int x, int y, int w, int h) { gfx_heal(x, y + POP_YOFF, w, h); }
|
||||
|
||||
static void env_b(uint8_t id, int x, int yb) { blit_b(&env[id >> 5], (uint8_t)(id & 31), x, yb); }
|
||||
static void wall_b(uint8_t id, int x, int yb) { blit_b(&wall_at, id, x, yb); }
|
||||
static void fore_b(uint8_t id, int x, int yb) { blit_b(&fore_at, id, x, yb); }
|
||||
@@ -129,8 +132,15 @@ static const uint8_t RPOS[4] = {52, 42, 31, 21};
|
||||
|
||||
/* ---- Текущая комната (globals для tile_code/mod) ------------------- */
|
||||
static const uint8_t *g_fg, *g_bg, *g_lfg, *g_lbg;
|
||||
static const uint8_t *g_below; /* row0-коды комнаты СНИЗУ (для topright под нижним рядом) */
|
||||
static uint8_t g_room;
|
||||
|
||||
/* Задать ряд 0 комнаты снизу (fg-коды) — нужен для draw_tile_topright/
|
||||
* floorright, когда тайл нижнего ряда стал видимым (empty после падения
|
||||
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
|
||||
* снизу (кромка уровня = стена). */
|
||||
void pop_room_set_below(const uint8_t *below_row0_fg) { g_below = below_row0_fg; }
|
||||
|
||||
static uint8_t tile_code(int row, int col)
|
||||
{
|
||||
if (col < 0) {
|
||||
@@ -287,6 +297,24 @@ static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy)
|
||||
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];
|
||||
|
||||
static const uint8_t LOOSE_FRAM_LEFT[12] = {41,69,41,70,70,41,41,41,70,70,70,0};
|
||||
static const uint8_t LOOSE_FRAM_RIGHT[12] = {42,71,42,72,72,42,42,42,72,72,72,0};
|
||||
static const uint8_t LOOSE_FRAM_BOTTOM[12] = {43,73,43,74,74,43,43,43,74,74,74,0};
|
||||
|
||||
/* get_loose_frame (seg008:761): индекс дрожащего кадра по модификатору. */
|
||||
static uint8_t get_loose_frame(uint8_t m)
|
||||
{
|
||||
if (m & 0x80) { m &= 0x7F; if (m > 10) return 1; }
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||||
static void draw_tile(int row, int col)
|
||||
{
|
||||
@@ -302,9 +330,14 @@ static void draw_tile(int row, int col)
|
||||
uint8_t rbl_code;
|
||||
uint8_t wmod;
|
||||
|
||||
/* тайл снизу-слева (для topright) */
|
||||
if (row + 1 > 2) rbl_code = 1; /* ниже нижнего ряда = пол */
|
||||
else rbl_code = tile_code(row + 1, col - 1);
|
||||
/* тайл снизу-слева (для 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;
|
||||
if (lc < 0) rbl_code = 20; /* кромка уровня = стена */
|
||||
else rbl_code = g_below ? (uint8_t)(g_below[lc] & 0x1F) : 1;
|
||||
} else rbl_code = tile_code(row + 1, col - 1);
|
||||
|
||||
/* --- floorright (seg008.c:392) — только can_see_bottomleft --- */
|
||||
if (code == 0 || code == 9 || code == 12 || code == 26) {
|
||||
@@ -337,8 +370,8 @@ static void draw_tile(int row, int col)
|
||||
/* --- draw_tile_anim_right (seg008.c:530) — статичные части --- */
|
||||
if (lcode == 4) { /* gate: решётка */
|
||||
draw_gate_back(lmod, xh, dby, dmy);
|
||||
} else if (lcode == 11) { /* loose: правый край */
|
||||
env_b(42, x, dby - 1);
|
||||
} else if (lcode == 11) { /* loose: правая грань (анимир. по modif соседа-loose) */
|
||||
env_b(LOOSE_FRAM_RIGHT[get_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])
|
||||
@@ -354,14 +387,15 @@ static void draw_tile(int row, int col)
|
||||
env_b(t->bottom_id, x, dby);
|
||||
}
|
||||
|
||||
/* --- draw_loose (seg008.c:600) — статич. кадр --- */
|
||||
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif) --- */
|
||||
if (code == 11)
|
||||
env_b(43, x, dby); /* loose_fram_bottom[0] */
|
||||
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])], x, dby);
|
||||
|
||||
/* --- draw_tile_base (seg008.c:611) --- */
|
||||
{
|
||||
uint8_t base_id = t->base_id;
|
||||
if (code == 11) base_id = 41; /* loose_fram_left[0] */
|
||||
if (code == 11) /* loose: левая грань (анимир.) */
|
||||
base_id = LOOSE_FRAM_LEFT[get_loose_frame(pop_loose_modif[row * 10 + col])];
|
||||
if (base_id)
|
||||
env_b(base_id, x, t->base_y + dmy);
|
||||
}
|
||||
@@ -376,6 +410,149 @@ static void draw_tile(int row, int col)
|
||||
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)
|
||||
{
|
||||
int x = COL_XH[col] * 8;
|
||||
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
|
||||
* перерисовка обоих тайлов с дрожащим кадром (draw_tile берёт кадр из
|
||||
* pop_loose_modif). Порт redraw_tile_height (loose + правый сосед).
|
||||
* NB: печёный кадр-0 может слегка просвечивать по кромкам — чистое
|
||||
* решение (bake тайла как empty + рисовать loose всегда динамически)
|
||||
* отложено; wipe-в-чёрный делал плиту невидимой — не годится. */
|
||||
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);
|
||||
}
|
||||
|
||||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||||
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
|
||||
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
|
||||
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
|
||||
void pop_loose_bake_empty(int row, int col)
|
||||
{
|
||||
int x = COL_XH[col] * 8;
|
||||
int xr = COL_XH[col + 1] * 8;
|
||||
int yb = 63 * row + 46;
|
||||
/* Чёрная плита (opaque NORMAL — пишет в VRAM И ОЗУ-копию акселератора):
|
||||
* стирает ЗАПЕЧЁННЫЙ пиксель-в-пиксель loose-пол с обоих тайлов, иначе
|
||||
* heal вернул бы старый пол из ОЗУ-копии. Ширина 40 — кусок пола
|
||||
* заходит на x+32..+40. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(x, yb + POP_YOFF, x + 39, yb + 28 + POP_YOFF); /* сам тайл: +40 (кусок пола заходит вправо; сосед перерисуется) */
|
||||
if (col + 1 < 10)
|
||||
bar(xr, yb + POP_YOFF, xr + 31, yb + 28 + POP_YOFF); /* сосед: РОВНО 32 — не залезть в col+2 (его не перерисовываем) */
|
||||
/* Новый статич. фон: сам тайл (empty → «стена вниз» topright комнаты
|
||||
* снизу) + правый сосед (его левая грань: был loose-край env_b(42),
|
||||
* теперь пусто). TRANSPARENT — 0xFF не забивает чёрный, пишет и в
|
||||
* ОЗУ-копию → heal сохранит. */
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
draw_tile(row, col);
|
||||
if (col + 1 < 10)
|
||||
draw_tile(row, col + 1);
|
||||
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. */
|
||||
static uint8_t mob_active; /* 0 нет; 1 падает; 2 = очистка второй страницы */
|
||||
static int mob_x; /* левый край (COL_XH*8) */
|
||||
static int mob_y; /* main-y (dmy покоя), растёт вниз */
|
||||
static int8_t mob_speed;
|
||||
static int8_t mob_row, mob_col; /* тайл-позиция куска (для окклюзии соседа) */
|
||||
|
||||
void pop_loose_mob_spawn(int row, int col)
|
||||
{
|
||||
mob_x = COL_XH[col] * 8;
|
||||
mob_y = 63 * row + 62; /* dmy покоя — откуда отрывается кусок */
|
||||
mob_row = (int8_t)row;
|
||||
mob_col = (int8_t)col;
|
||||
mob_speed = 0;
|
||||
mob_active = 1;
|
||||
}
|
||||
|
||||
/* Габарит куска по 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) */
|
||||
|
||||
void pop_loose_mob_tick(void)
|
||||
{
|
||||
if (!mob_active) return;
|
||||
/* стереть прошлый кусок этой страницы + вернуть печёный фон коридора
|
||||
* (фикс rect [160,192] по ВСЕЙ ширине куска; overflow прошлого кадра
|
||||
* стёрт его же clip'ом той же ширины). */
|
||||
heal_off(MOB_X0(mob_x), 160, MOB_W, 32);
|
||||
if (mob_active == 2) { mob_active = 0; return; } /* очистили вторую стр. — всё */
|
||||
/* гравитация (move_loose): ускорение до 29 */
|
||||
if (mob_speed < 29) mob_speed += 3;
|
||||
mob_y += mob_speed;
|
||||
if (mob_y - 24 >= POP_PLAYFIELD_H) { mob_active = 2; return; } /* весь ниже поля → +1 кадр очистки */
|
||||
/* Падающий кусок (SDLPoP draw_mobs/add_midtable, case 0x80): ТРИ куска —
|
||||
* left(41) на obj_y-3, bottom(43) на obj_y, right(42) на obj_x+4 (=col+1,
|
||||
* заходит в тайл СПРАВА 2,7) на obj_y-1. У нас bottom=dby(mob_y+3) →
|
||||
* left=mob_y, right=mob_y+2 в mob_x+32 (col+1). */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
env_b(43, mob_x, mob_y + 3);
|
||||
env_b(41, mob_x, mob_y);
|
||||
env_b(42, mob_x + 32, mob_y + 2);
|
||||
/* Окклюзия правого куска соседним полом (SDLPoP redraw_at_cur_mob:
|
||||
* set_redraw_full(curr_tilepos+1) — сосед перерисовывается ПОВЕРХ mob).
|
||||
* Правый кусок 42 целиком лежит в тайле col+1; пока плита у уровня пола,
|
||||
* её правый край ПРЯЧЕТСЯ за передней гранью пола 2,7. Перерисовываем
|
||||
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
|
||||
* до heal'а следующего кадра — виден как хвост во время падения). */
|
||||
if (mob_col + 1 < 10)
|
||||
draw_tile(mob_row, mob_col + 1);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за низ поля (в статус-полосу) — по ВСЕЙ ширине куска */
|
||||
pop_clip_sprite(MOB_X0(mob_x), MOB_W, mob_y - 23 + POP_YOFF, 30);
|
||||
}
|
||||
|
||||
/* Спрайт вышел за поле? → взвести флаг на 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!border_dirty) return;
|
||||
border_dirty--;
|
||||
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)
|
||||
|
||||
@@ -13,6 +13,18 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- Вертикальный layout (PoP-экран 320×200 в нашем 320×256) --------- *
|
||||
* Игровой экран 200px центрируем по высоте: сдвиг всего рисунка вниз на
|
||||
* POP_YOFF = (256-200)/2 = 28px (чёрные борта сверху/снизу). Внутри 200px
|
||||
* верхние POP_PLAYFIELD_H = 192 — поле тайлов/спрайтов (комната = 3 ряда),
|
||||
* нижние 8 — статус-полоса (жизни/сообщения): тайлы/спрайты туда НЕ лезут.
|
||||
* Сдвиг применяется на границе примитивов (blit/heal) — логика рисования
|
||||
* остаётся в «room-space» y=0..191. */
|
||||
#define POP_YOFF 28
|
||||
#define POP_PLAYFIELD_H 192
|
||||
/* Экранный y нижней границы поля (ниже — статус-полоса + борт → клип). */
|
||||
#define POP_CLIP_BOTTOM (POP_YOFF + POP_PLAYFIELD_H)
|
||||
|
||||
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
|
||||
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
|
||||
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
|
||||
@@ -29,6 +41,11 @@ 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);
|
||||
|
||||
/* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright,
|
||||
* когда тайл нижнего ряда стал empty (после падения loose): «стена вниз»
|
||||
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
|
||||
void pop_room_set_below(const uint8_t *below_row0_fg);
|
||||
|
||||
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
|
||||
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
|
||||
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
|
||||
@@ -36,4 +53,29 @@ void pop_room_draw(uint8_t room_num,
|
||||
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir, uint8_t frame, uint8_t action);
|
||||
|
||||
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
|
||||
* pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick.
|
||||
* shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту
|
||||
* после падения (NORMAL, в ОЗУ-копию). */
|
||||
void pop_loose_shake_draw(int row, int col);
|
||||
void pop_loose_bake_empty(int row, int col);
|
||||
|
||||
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
|
||||
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
|
||||
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
|
||||
void pop_loose_mob_spawn(int row, int col);
|
||||
void pop_loose_mob_tick(void);
|
||||
|
||||
/* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом
|
||||
* sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше
|
||||
* верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра.
|
||||
* Звать СРАЗУ после блита падающего объекта (Kid/кусок). */
|
||||
void pop_clip_sprite(int x, int w, int sy_top, int h);
|
||||
|
||||
/* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах,
|
||||
* где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite):
|
||||
* даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать
|
||||
* КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */
|
||||
void pop_room_clip_borders(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_kid.h"
|
||||
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
|
||||
#include "kid_data.h"
|
||||
|
||||
kid_t Kid;
|
||||
|
||||
/* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN
|
||||
* в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */
|
||||
int8_t knock;
|
||||
|
||||
/* Координатные таблицы (data.h). */
|
||||
static const uint8_t x_bump[20] = {
|
||||
(uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
||||
@@ -108,7 +113,11 @@ void play_seq(void)
|
||||
Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
||||
Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
||||
break;
|
||||
case 0xF5: case 0xF4: /* KNOCK up/down */
|
||||
case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */
|
||||
knock = 1;
|
||||
break;
|
||||
case 0xF4: /* KNOCK_DOWN: приземление трясёт loose В СВОЁМ РЯДУ */
|
||||
knock = -1;
|
||||
break;
|
||||
case 0xF2: /* SOUND — потребить 1 байт */
|
||||
Kid.curr_seq++;
|
||||
@@ -186,7 +195,7 @@ void kid_heal(void)
|
||||
{
|
||||
uint8_t p = gfx_get_draw_page();
|
||||
if (kid_lvalid[p])
|
||||
gfx_heal(kid_lx[p], kid_ly[p], kid_lw[p], kid_lh[p]);
|
||||
gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */
|
||||
kid_lvalid[p] = 0;
|
||||
}
|
||||
|
||||
@@ -229,8 +238,11 @@ void kid_draw(void)
|
||||
top = obj_y - (int)h + 1 + KID_YOFF;
|
||||
uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */
|
||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||||
gfx_blit_cols(bx, top, img, flip);
|
||||
gfx_blit_cols(bx, top + POP_YOFF, img, flip); /* +YOFF: центрирование */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
||||
* узко, только по прямоугольнику Кида и только при выходе. */
|
||||
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
||||
kid_lx[dp] = bx; kid_ly[dp] = top;
|
||||
kid_lw[dp] = w; kid_lh[dp] = h; kid_lvalid[dp] = 1;
|
||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
||||
|
||||
@@ -26,6 +26,10 @@ typedef struct {
|
||||
|
||||
extern kid_t Kid;
|
||||
|
||||
/* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN
|
||||
* (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */
|
||||
extern int8_t knock;
|
||||
|
||||
/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph.
|
||||
* 0 — OK, -1 — ошибка. */
|
||||
int pop_kid_load(uint8_t npages);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "pop_kid.h"
|
||||
#include "pop_map.h"
|
||||
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
|
||||
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
|
||||
|
||||
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
|
||||
#define TILE_EMPTY 0
|
||||
@@ -28,6 +29,8 @@
|
||||
#define TILE_MIRROR 13
|
||||
#define TILE_CHOMPER 18
|
||||
#define TILE_WALL 20
|
||||
#define TILE_LOOSE 11 /* проваливающийся пол */
|
||||
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
||||
#define ACT_STAND 0
|
||||
#define ACT_RUNJUMP 1
|
||||
#define ACT_HANGCLIMB 2
|
||||
@@ -82,12 +85,12 @@ static int8_t g_infrontx; /* колонка «перед персонаже
|
||||
|
||||
/* ---- Карта комнаты ------------------------------------------------- */
|
||||
uint8_t pop_fell_out; /* 1 = Kid упал за низ комнаты (сброс приложением) */
|
||||
static const uint8_t *g_fg; /* fg[30], row*10+col */
|
||||
static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ — loose→empty) */
|
||||
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
|
||||
static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */
|
||||
|
||||
void pop_map_set(const uint8_t *fg) { g_fg = fg; }
|
||||
void pop_map_set(uint8_t *fg) { g_fg = fg; }
|
||||
|
||||
static uint8_t get_tile(int8_t col, int8_t row)
|
||||
{
|
||||
@@ -274,18 +277,26 @@ static void start_fall(void)
|
||||
kid_set_seq(seq);
|
||||
play_seq();
|
||||
determine_col();
|
||||
if (get_tile_at_char() == TILE_WALL) {
|
||||
in_wall();
|
||||
return;
|
||||
}
|
||||
/* Хвост seg006:1099: тайл ПЕРЕД персонажем — стена → отодвинуть на 1
|
||||
* назад (иначе передний край зайдёт в грань). (seq_104 для frame 44 с
|
||||
* малым зазором не портирован — в этом тесте не встречается.) */
|
||||
tile = get_tile_infrontof_char();
|
||||
if (tile == TILE_WALL) {
|
||||
if (frame != 44 || distance_to_edge_weight() >= 6)
|
||||
Kid.x = (uint8_t)char_dx_forward(-1);
|
||||
determine_col();
|
||||
/* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2).
|
||||
* Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose)
|
||||
* наш get_tile отдаёт WALL как СЕНТИНЕЛ кромки уровня, но в оригинале там
|
||||
* КОМНАТА СНИЗУ (у дыры — пол/пусто, не стена). Без гарда in_wall() ложно
|
||||
* срабатывал и откатывал X назад (6−d) → дайв падения смещался на тайл
|
||||
* влево (Кид «клевал» рано над колонной вместо дыры). */
|
||||
if (Kid.curr_row <= 2) {
|
||||
if (get_tile_at_char() == TILE_WALL) {
|
||||
in_wall();
|
||||
return;
|
||||
}
|
||||
/* Хвост seg006:1099: тайл ПЕРЕД персонажем — стена → отодвинуть на 1
|
||||
* назад (иначе передний край зайдёт в грань). (seq_104 для frame 44 с
|
||||
* малым зазором не портирован — в этом тесте не встречается.) */
|
||||
tile = get_tile_infrontof_char();
|
||||
if (tile == TILE_WALL) {
|
||||
if (frame != 44 || distance_to_edge_weight() >= 6)
|
||||
Kid.x = (uint8_t)char_dx_forward(-1);
|
||||
determine_col();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +627,116 @@ static void check_bumped(void)
|
||||
determine_col();
|
||||
}
|
||||
|
||||
/* ---- Loose floors (seg007) ---------------------------------------- *
|
||||
* Состояние на тайл: 0 = покой; 0x80..0x83 = тряска (do_knock); 1..11 =
|
||||
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
|
||||
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
|
||||
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
|
||||
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
|
||||
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
|
||||
|
||||
/* make_loose_fall (seg007:0EF6): взвести отсчёт падения, если ещё не идёт. */
|
||||
static void make_loose_fall(int pos, uint8_t modifier)
|
||||
{
|
||||
if (pos < 0 || pos >= 30) return;
|
||||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) return;
|
||||
if ((g_fg[pos] & 0x20)) return; /* «solid» loose — не от шага */
|
||||
if ((int8_t)pop_loose_modif[pos] <= 0) /* покой/тряска, не отсчёт */
|
||||
pop_loose_modif[pos] = modifier;
|
||||
}
|
||||
|
||||
/* check_press (seg006:0EC8, упрощ.): Kid стоит на loose → make_loose_fall;
|
||||
* frame 79 (прыжок вверх) с loose СВЕРХУ → пробить сверху. */
|
||||
static void check_press(void)
|
||||
{
|
||||
uint8_t frame = Kid.frame, action = Kid.action;
|
||||
if (frame == 79) { /* прыжок вверх — тайл сверху */
|
||||
if (get_tile_above_char() == TILE_LOOSE)
|
||||
make_loose_fall((Kid.curr_row - 1) * 10 + Kid.curr_col, 1);
|
||||
return;
|
||||
}
|
||||
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR */
|
||||
if (action != ACT_BUMPED && action >= ACT_HANGCLIMB) return;
|
||||
if (!(kid_cur_flags() & FRAME_NEEDS_FLOOR)) return;
|
||||
if (get_tile_at_char() == TILE_LOOSE)
|
||||
make_loose_fall(Kid.curr_row * 10 + Kid.curr_col, 1);
|
||||
}
|
||||
|
||||
/* do_knock (seg007:0FE0) + loose_make_shake (seg007:0FB4): сотрясение от
|
||||
* приземления/удара трясёт ВСЕ loose-полы в ряду tile_row (не роняя их —
|
||||
* modif 0x80..0x83 = чистая тряска, сбрасывается в pop_loose_tick). Трясём
|
||||
* только из ПОКОЯ (modif==0): если тайл уже дрожит/падает — не перебиваем. */
|
||||
static void do_knock(int tile_row)
|
||||
{
|
||||
int col, pos;
|
||||
if (tile_row < 0 || tile_row > 2) return;
|
||||
for (col = 0; col < 10; col++) {
|
||||
pos = tile_row * 10 + col;
|
||||
if ((g_fg[pos] & 0x1F) != TILE_LOOSE) continue;
|
||||
if (pop_loose_modif[pos] == 0)
|
||||
pop_loose_modif[pos] = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
/* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl-
|
||||
* командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) →
|
||||
* ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */
|
||||
void pop_check_knock(void)
|
||||
{
|
||||
if (knock) {
|
||||
do_knock(Kid.curr_row - (knock > 0));
|
||||
knock = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
|
||||
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
|
||||
void pop_loose_tick(void)
|
||||
{
|
||||
int pos;
|
||||
for (pos = 0; pos < 30; pos++) {
|
||||
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
|
||||
pop_loose_bake_empty(pos / 10, pos % 10);
|
||||
loose_bake[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
|
||||
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
|
||||
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
|
||||
* страницы дабл-буфера — иначе на одной остаётся застывший
|
||||
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
|
||||
pop_loose_shake_draw(pos / 10, pos % 10);
|
||||
loose_rest[pos]--;
|
||||
continue;
|
||||
}
|
||||
if (pop_loose_modif[pos] == 0) continue;
|
||||
{
|
||||
uint8_t m = ++pop_loose_modif[pos];
|
||||
if (m & 0x80) { /* тряска (do_knock) */
|
||||
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */
|
||||
pop_loose_modif[pos] = 0;
|
||||
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */
|
||||
continue;
|
||||
}
|
||||
} else if (m >= 11) { /* loose_floor_delay — падение */
|
||||
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
|
||||
* Debris появляется ТОЛЬКО там, где приземлился падающий
|
||||
* кусок (mob), т.е. рядом ниже. Loose в нижнем ряду (2,6)
|
||||
* — кусок улетает из комнаты вниз, на месте остаётся пусто
|
||||
* (стена-вниз от тайла снизу + чёрный фон). */
|
||||
g_fg[pos] = TILE_EMPTY;
|
||||
pop_loose_modif[pos] = 0;
|
||||
pop_loose_bake_empty(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
|
||||
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
|
||||
pop_loose_mob_spawn(pos / 10, pos % 10); /* отрыв падающего куска (L4) */
|
||||
continue;
|
||||
}
|
||||
pop_loose_shake_draw(pos / 10, pos % 10); /* дрожащий кадр */
|
||||
}
|
||||
}
|
||||
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
|
||||
}
|
||||
|
||||
void pop_phys_tick(void)
|
||||
{
|
||||
if (grab_timer) grab_timer--;
|
||||
@@ -624,4 +745,6 @@ void pop_phys_tick(void)
|
||||
determine_col();
|
||||
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
|
||||
check_action();
|
||||
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
|
||||
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
|
||||
}
|
||||
|
||||
@@ -14,8 +14,22 @@
|
||||
#include <stdint.h>
|
||||
|
||||
/* Задать карту текущей комнаты: fg[30] = коды тайлов (row*10+col, 3×10).
|
||||
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). */
|
||||
void pop_map_set(const uint8_t *fg);
|
||||
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
|
||||
* МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию,
|
||||
* что и в pop_room_draw, чтобы отрисовка видела изменения. */
|
||||
void pop_map_set(uint8_t *fg);
|
||||
|
||||
/* Loose floors (проваливающиеся полы). pop_loose_modif[30] — состояние на
|
||||
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
|
||||
* отрисовке. pop_loose_tick() — анимация + перерисовка изменившихся тайлов
|
||||
* на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back),
|
||||
* рядом с kid-отрисовкой. */
|
||||
extern uint8_t pop_loose_modif[30];
|
||||
void pop_loose_tick(void);
|
||||
|
||||
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
|
||||
* приземления/удара. Зовётся из pop_phys_tick. */
|
||||
void pop_check_knock(void);
|
||||
|
||||
/* Установлен в 1, когда Kid упал ниже комнаты (комнаты снизу нет в этом
|
||||
* тесте). Приложение должно сбросить Kid (kid_init) и снять флаг. */
|
||||
|
||||
@@ -7,4 +7,7 @@ static const unsigned char room1_fg[30] = {0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x
|
||||
static const unsigned char room1_bg[30] = {0xFF,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00};
|
||||
static const unsigned char leftcol_fg[3] = {0x04,0x14,0x14};
|
||||
static const unsigned char leftcol_bg[3] = {0x01,0x00,0x00};
|
||||
/* Ряд 0 комнаты СНИЗУ (room 2, link Dn) — для topright «стены вниз» под
|
||||
* упавшим loose (2,6): room2(0,5)=0x14 стена. Из res2002.bin row0. */
|
||||
static const unsigned char belowrow_fg[10] = {0x14,0x14,0x00,0x00,0x00,0x14,0x01,0x03,0x01,0x03};
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,11 @@ int main(void)
|
||||
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
|
||||
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
|
||||
uint8_t pg, front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
|
||||
/* МУТАБЕЛЬНАЯ копия комнаты — одна на отрисовку (pop_room_draw) И
|
||||
* коллизию (pop_map): loose-пол при падении меняет тайл на empty, и
|
||||
* оба модуля должны это видеть. */
|
||||
uint8_t room_fg[30];
|
||||
for (pg = 0; pg < 30; pg++) room_fg[pg] = room1_fg[pg];
|
||||
|
||||
if (pop_bg_load() != 0) { /* атласы — ДО initgraph */
|
||||
puts("pop_bg_load failed");
|
||||
@@ -44,17 +49,18 @@ int main(void)
|
||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
|
||||
pop_room_set_below(belowrow_fg); /* ряд снизу (room 2) для «стены вниз» */
|
||||
for (pg = 0; pg < 2; pg++) {
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
pop_room_draw(1, room1_fg, room1_bg, leftcol_fg, leftcol_bg);
|
||||
pop_room_draw(1, room_fg, room1_bg, leftcol_fg, leftcol_bg);
|
||||
}
|
||||
gfx_set_draw_page(0);
|
||||
|
||||
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
|
||||
* ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход. */
|
||||
pop_map_set(room1_fg); /* карта для коллизии/падения (K3) */
|
||||
pop_map_set(room_fg); /* карта для коллизии/падения (K3) */
|
||||
kid_init(SEQ_STAND, 1, 1, 0);
|
||||
kid_draw();
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
@@ -102,10 +108,13 @@ int main(void)
|
||||
pop_fell_out = 0;
|
||||
kid_init(SEQ_STAND, 1, 1, 0);
|
||||
}
|
||||
kid_draw(); /* нарисовать новый кадр */
|
||||
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
|
||||
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
|
||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
kid_fp_width(), kid_fp_height(),
|
||||
Kid.direction, Kid.frame, Kid.action);
|
||||
pop_room_clip_borders(); /* почистить борта — только в кадрах падения (гейт) */
|
||||
|
||||
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
|
||||
* сделан (скрытая страница), wait_vsync ловит начало кадра,
|
||||
|
||||
@@ -50,6 +50,12 @@ FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100}
|
||||
# попадает в used render_room, добавляем явно в env-фон. (32 обычно уже есть.)
|
||||
CLIMB_OVERLAY_ENV_IDS = {32, 150, 151}
|
||||
|
||||
# Дрожащие/падающие кадры loose-плиты (loose_fram_left/right/bottom, seg008):
|
||||
# 41/42/43 = покой (уже в used из render_room), 69-74 = вибрация/раздвоение —
|
||||
# используются ТОЛЬКО динамикой (shake/fall), в статический render_room НЕ
|
||||
# попадают, поэтому добавляем явно. Без них дрожащая плита невидима.
|
||||
LOOSE_ANIM_ENV_IDS = {69, 70, 71, 72, 73, 74}
|
||||
|
||||
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
|
||||
ENV_MASK = (1 << ENV_SHIFT) - 1
|
||||
|
||||
@@ -241,7 +247,8 @@ def main():
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
used = collect_used()
|
||||
env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL)
|
||||
| CLIMB_OVERLAY_ENV_IDS) # + пол-оверлей подъёма
|
||||
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
|
||||
| LOOSE_ANIM_ENV_IDS) # + дрожащие кадры loose
|
||||
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
|
||||
|
||||
env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]
|
||||
|
||||
Reference in New Issue
Block a user