PoP: roomtest — объекты/обломки/переходы комнат + арт

Порт Prince of Persia (applications/PoP/roomtest): развитие уровня,
объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка;
планы (room_model/size_optimization), bug_list, pop_trob.
Арт third_party/16x16-RPG-characters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 19:38:23 +03:00
parent 3d586af031
commit 86d7615841
33 changed files with 2199 additions and 64 deletions
+68
View File
@@ -0,0 +1,68 @@
# PoP roomtest — модель `kid_room ≠ drawn_room` (баг #4)
Порт straddle-модели SDLPoP: персонаж может находиться в СОСЕДНЕЙ комнате,
пока на экране ещё ТЕКУЩАЯ (drawn_room). Источник истины — SDLPoP.
## Факты из SDLPoP (подтверждено чтением исходника)
- `Char.room` (реальная комната персонажа) ≠ `drawn_room` (отрисованная) —
штатное состояние.
- **Коллизия через ±140:** `xpos_in_drawn_room()` (seg004:0405) сдвигает
xpos на `±TILE_SIZEX*SCREEN_TILECOUNTX = ±140`, когда `curr_room` колонки
(`curr_row_coll_room[col]`) ≠ `drawn_room` (room_L/room_BL → 140,
room_R/room_BR → +140). Т.е. коллизия строится по РЕАЛЬНЫМ тайлам соседей.
- **Смена экрана:** `check_the_end()` (seg000:0FBD): `if (next_room!=0 &&
next_room!=drawn_room) { drawn_room=next_room; load_room_links; redraw }`.
`next_room` ставится в `exit_room()` (= `Char.room` ПОСЛЕ успешного
`leave_room`). Значит drawn_room следует за Char.room, но Char.room меняется
ТОЛЬКО при реальном пересечении шва (leave_room, seg002:0504) на «легальном»
кадре/действии (не turn/climb/standup).
- **Отрисовка левого соседа:** только `load_leftroom()` (col9 левого соседа в
левую кромку); правый сосед НЕ рисуется (изометрия). Окклюзия ворот на шве —
только левая (seg008:696).
- **Ceiling-полоса:** `draw_room` рисует доп. ряд из `room_A` (row2, draw_main_y
=-1). (Уже реализовано, баг #3.)
## Текущее состояние нашего движка (до #4)
`cur_room` (=drawn_room) ВСЕГДА == комната Kid. Шов подделан: Kid остаётся в
drawn_room с `curr_col=-1/10` + снапшоты соседей `g_lcol/g_rcol` (коллизия ±1
кол) / `lcol_bg` (openness ворот). Уход из комнаты — `pop_leave_dir`/`enter_room`
МГНОВЕННО при пересечении порога `char_x`. Отсюда #4: экран переключается
раньше, чем в оригинале (Kid должен «отступить» за кромку, оставив старую
комнату).
## План (инкременты, каждый проверяется в MAME)
### S1. Данные + рендер-смещение Kid
- Ввести `kid_room` (реальная комната Kid) отдельно от `cur_room`(=drawn_room).
- `kid_x_offset()` = разница комнат: kid_room == left(drawn) → лог. x Kid 140
(рисуется за левой кромкой); right → +140; равны → 0. (порт
xpos_in_drawn_room).
- `kid_draw`/heal/fore используют смещение (Kid рисуется частично за кромкой).
- Проверка: Kid у шва рисуется со сдвигом, экран не дёргается.
### S2. Коллизия по kid_room
- Коллизионный контекст (`g_fg`/edges/`g_room`/modif в pop_map) следует за
`kid_room`, а не за drawn_room. Когда kid_room≠drawn_room — грузим
соседа как коллизионную комнату (curr_col 0..9 в кадре kid_room).
- Отрисовка (room_fg и т.п.) остаётся по drawn_room.
- Порт `curr_row_coll_room[]`/`xpos_in_drawn_room` можно упростить: держим
ОДИН коллизионный room (kid_room) + существующие снапшоты кромок для ±1 кол.
### S3. Отложенная смена drawn_room
- Уход (`check_leave`/`check_leave_below`): ставит `kid_room=сосед`,
репроецирует Kid (x∓140, col∓10) — но drawn_room НЕ меняет сразу.
- `check_the_end`-эквивалент в главном цикле: `if (kid_room != drawn_room &&
<условие коммита>) enter_room(kid_room)`. Условие коммита — по SDLPoP:
как только Char.room сменилась легальным leave (не bumped/turn). Для
«bumped назад за кромку» drawn_room остаётся (симптом #4).
- Проверка сценариев #4/#5 в MAME.
### S4. Полировка
- Окклюзия/ceiling у шва при straddle, BUG-OCCL-1 (глубина), правый край.
## Связанные баги (bug_list.md)
BUG-CEIL-1 (руки при прыжке вверх), BUG-CEIL-2 (loose в потолке),
BUG-CEIL-3 (потолок над анимируемыми воротами), BUG-OCCL-1 (тень дальней
колонны). Memory: `pop_seam_room_model`.
@@ -0,0 +1,258 @@
# roomtest — план оптимизации по размеру + переход на huge/banking
Статус: **план для отдельной сессии** (2026-07-21). Документ самодостаточный
(рассчитан на старт с пустого контекста). Цель — освободить место: сейчас
`applications/PoP/roomtest` в режиме `small` почти упёрся в потолок 32 КБ.
Правило проекта (`applications/PoP/CLAUDE.md`): механику/раскладку памяти
сверять с исходником и с memory (`sprinter_memory_modes`, `sdcc_banking`,
`bank_local_data_pattern`, `pop_banking_architecture`). Перед оптимизацией —
`make size-check`-подобный замер до/после (здесь — руками по `.map`).
---
## 0. Как мерить
- Сборка: `cd applications/PoP/roomtest && make roomtest.exe` (режим `small`,
`--gfx 256`). Карта символов — `.sprinter-cc-roomtest/roomtest.map`
(адреса сдвигаются при каждой пересборке!).
- Размеры областей — из `.map` (`_CODE`, `_DATA`, `_BSS`).
- Вклад модулей в `_CODE` — атрибуция диапазонов между символами по модулю
(скрипт-однострочник на python в истории; группировать символы `.map` по
3-й колонке-модулю и суммировать `addr[i+1]-addr[i]`).
- MAME-проверка после изменений раскладки ОБЯЗАТЕЛЬНА (режимы памяти —
типовой источник «молча ломается», см. `sprinter_memory_modes`).
## 1. ТЕКУЩЕЕ СОСТОЯНИЕ (замер 2026-07-21)
Режим `small` = единое пространство **W1+W2 = 0x4000..0xBFFF (32 КБ)**; CODE с
0x4100, DATA/BSS/heap цепляются ЗА CODE автоматически (`--data-loc 0` =
linker chains), стек — вверху W2.
| Область | Размер | Диапазон |
|---------|--------|----------|
| `_CODE` | ~27 250 Б (0x6A6F) | 0x41000xAB6F |
| `_HOME` | 227 Б | 0xAB6F |
| `_DATA` | 3 449 Б (0x0D79) | 0xAC780xB9F1 |
| `_BSS` | 290 Б | |
**Образ ≈ 31.2 КБ; до верха W2 (0xBFFF) остаётся ≈ 1.3 КБ на кучу+стек.**
Куча в roomtest почти не используется (атласы/уровень — в EMM-страницах),
но запас критично мал.
### Вклад модулей в _CODE (по .map, приблизительно)
```
7003 pop_bg (вся отрисовка тайлов/слоёв/wall_pattern)
6326 pop_kid (из них ~3745 Б — СТАТ. ТАБЛИЦЫ kid_data.h, см. ниже)
3762 pop_map (коллизия/физика/пики)
1455 pop_trob (кнопки/ворота/пики-каркас)
1224 pop_level (загрузка уровня, doorlink)
914 roomtest (главный цикл)
~7000 libc/libbgi (gfx_blit*, atlas_load, kbd_raw, open/read, irq, div/mul…)
```
### Крупные СТАТИЧЕСКИЕ данные (сейчас в _CODE как `const`)
- **`kid_data.h` — самый большой кусок, ~3.7 КБ**, живёт в _CODE (атрибутируется
pop_kid):
- `kid_seqtbl[2310]` — байткод последовательностей (play_seq).
- `kid_frames[241]` × 5 Б = 1205 Б — таблица кадров (image,dx,dy,flags,sword).
- `kid_seq_off[115]` × 2 Б = 230 Б — смещения seq.
- `pop_bg`: `tile_table[31]`×12 = 372 Б + ~20 мелких const-таблиц (COL_XH,
WALL_FRAM_*, SPIKES_FRAM_{RIGHT,LEFT,FORE}, LOOSE_FRAM_*, DOOR_FRAM_SLICE,
BLUELINE_*, LPOS/RPOS, FLOOR_LEFT_OVERLAY) — суммарно ~0.50.7 КБ.
- `pop_map`: `x_bump[20]`, `y_land[5]`, `wall_dl/dr`, `dir_front/behind` — ~100 Б.
- В `_DATA` (W2, не CODE): `room_modif[24][30]`=720 Б + копии LINKLOC/LINKMAP=512 Б
(pop_trob/pop_level) + рабочие массивы roomtest.
---
## 2. ПУТЬ A — оптимизация КОДА (без смены модели)
1. **Компиляторные флаги** (`bin/sprinter-cc`): попробовать `--opt-code-size`
у SDCC и подобрать `--max-allocs` (сейчас дефолт 100000; меньше = мельче код,
но медленнее компиляция; см. `mdview2_size_budget` — там `--max-allocs`
давал −1.4 КБ). Замерить каждый модуль отдельно.
2. **Дедуп подстановки нажатой кнопки**: логика `opener→floor / closer→stuck`
по таймеру связи ПРОДУБЛИРОВАНА в `draw_tile` и `fore_tile` (pop_bg.c).
Вынести в `static inline`/helper `subst_pressed_button(code,mod)`.
3. **wall_pattern / prandom** (pop_bg): 32-битный LCG (`unsigned long`) —
пользователь не любит 32-бит (см. `avoid_32bit_arith_z80`); но это PRNG
оригинала (нужен для совпадения раскладки стен) — трогать осторожно, только
если найдётся 16-битный эквивалент, дающий ТУ ЖЕ последовательность.
4. **Ревизия дублей**: `y_to_row` определён в pop_bg И pop_map; мелкие
геометрические хелперы дублируются — свести в один internal-модуль.
5. `/simplify`-проход по последним правкам Фазы B (pop_trob/pop_bg).
Ожидаемый выигрыш пути A: единицы–первые сотни байт на пункт; в сумме,
оптимистично, ~1–2 КБ. Недостаточно как единственная мера.
---
## 3. ПУТЬ B — вынос СТАТ. ДАННЫХ в EMM-страницы (с атласами / с level)
**Идея (по замечанию пользователя):** EMM-страницы атласов и уровня
использованы лишь частично (страница 16 КБ, данных меньше), в «хвосте» —
свободное место. Часть `const`-таблиц можно хранить ТАМ, а не в _CODE/_DATA,
если таблица читается ИМЕННО ТОГДА, когда нужная страница уже в W0.
**Механика W0:** атласы блитятся из W0 (`_gfx_w0_state`: `_gfx_w0_cur`
спрайт-страница в W0; ISR-стаб `_gfx_w0_isr` возвращает её после прерывания).
Уровень (pop_level) маппит свою страницу в W0 на время извлечения
(`gfx_w0_map`/`gfx_w0_unmap`). → пока страница в W0, CPU может читать и
данные из неё по адресам 0x0000..0x3FFF.
**Категоризация таблиц по W0-контексту (задача сессии — уточнить по каждой):**
- **(a) Читается, когда в W0 АТЛАС** → хранить в свободном хвосте атлас-страницы.
Кандидаты — таблицы, которые нужны В МОМЕНТ блита конкретного атласа.
ГРАБЛИ: `draw_tile` читает `tile_table`/`COL_XH` ДО блита (чтобы решить, какой
спрайт/куда) — в этот момент в W0 может быть ДРУГАЯ страница (DSS/предыдущий
атлас). Т.е. большинство draw-таблиц читаются ВНЕ W0-атлас-контекста →
«в лоб» не переносятся. Нужен аудит КАЖДОГО чтения: гарантирована ли нужная
страница в W0 в этот тик.
- **(b) Читается, когда в W0 LEVEL** → хранить с уровнем (в его странице; там
~13.7 КБ свободно из 16). Кандидаты: константы декода doorlink, разбор
комнат — всё, что pop_level делает под `gfx_w0_map(lvl_page)`.
- **(c) Нужна и там, и там** → дублировать в обеих страницах ЛИБО оставить
резидентной (если дубли дороже экономии).
- **(d) Читается в чистой ЛОГИКЕ (W0 не важен)** → перенос требует ЯВНОГО
`gfx_w0_map` на каждое чтение (дорого, особенно в горячих циклах) → как
правило оставить резидентной.
**Отдельно `kid_data.h` (3.7 КБ — самый жирный кандидат):**
- `kid_frames`/`kid_seqtbl` читаются в `play_seq` (ЧИСТАЯ логика, каждый тик) И
в `kid_draw` (блит из kid-атласа, kid-страница в W0). Т.е. частично (a),
частично (d). Перенос всей таблицы в kid-атлас-страницу заставит `play_seq`
делать `gfx_w0_map` на каждый шаг байткода → замерить стоимость (может убить
бюджет спрайтов, см. `sprite_engine_perf`). Вариант: держать в EMM отдельной
страницей данных Kid и маппить один раз на кадр вокруг kid_tick+kid_draw.
- Это самый большой одиночный выигрыш (−3.7 КБ из _CODE), но и самый рискованный
по скорости — приоритетный к ПРОТОТИПИРОВАНИЮ и замеру.
**Паттерн переноса writable/const данных в банк/страницу:** см. memory
`bank_local_data_pattern` (--codeseg/--constseg/--dataseg BANKn + trampoline-fix
+ mkexe -p 0) и `sdcc_static_storage_gotcha`.
### 3.1 Свободное место в страницах (замер 2026-07-21, страница = 16384 Б)
```
BG-атласы: размер свободно
pop_env0.atl 10578 5806
pop_env1.atl 12449 3935 <- САМАЯ ТЕСНАЯ из bg
pop_env2.atl 10798 5586
pop_env3.atl 5032 11352 <- много места
pop_env4.atl 8498 7886
pop_wall.atl 11543 4841
pop_fore.atl 7763 8621
Kid-атласы (28 стр): free min=6161 max=15452 avg=9722
Level (res2001.bin): данные 2305, свободно ~13823 (16384 0x100 стаб 2305)
```
**Выводы по вместимости:**
- **Макс. данных в ОДНОМ атлас-банке = свободный хвост ЭТОЙ страницы** (см.
таблицу). Связывающее ограничение — самая тесная нужная страница (env1 =
3935 Б; не перегружать её).
- Если страница будет маппиться в **W0** — минус ~0x100 Б на ISR-стаб (как
level). Атлас-страницы стаб УЖЕ содержат (atlas_load патчит) → данные класть
в хвост ПОСЛЕ атласа.
- **`kid_data.h` (3.7 КБ) влезает в kid-страницу** (min free 6161) или в
отдельную выделенную страницу данных Kid — предпочтительно отдельную (маппить
раз на кадр, не конфликтуя с kid-атласами блита).
- **Level-таблицы** — вагон места в level-странице (~13.8 КБ).
- **BG draw-таблицы** (~0.7 КБ) влезут в env3/fore/env4 (много free), НО см.
граблю W0-контекста в §3(a) — читаются ли они, когда нужная страница в W0.
- **Выделенная страница ТОЛЬКО под данные** (не делить с атласом) = до ~16 КБ
(−0x100 стаб при W0-маппинге). EMM-бюджет это позволяет (см.
`sprinter_emm_budget`: 215/3440 КБ free на старте).
- **Принудительно уменьшать макс. атлас (репак мельче) — КРАЙНИЙ случай:** это
резко поднимет число атлас-банков (сейчас 5 env-страниц адресуются как id>>5;
дробление ломает эту адресацию и множит страницы). Сначала использовать
СУЩЕСТВУЮЩИЙ свободный хвост и отдельные data-страницы.
---
## 4. ПУТЬ C — переход на huge (banked code)
### 4.1 Что такое huge сейчас (`bin/sprinter-cc`, `runtime/crt0_banked`)
- `--memory huge`: `MODE_CODE_LOC=0x4100`, **`MODE_DATA_LOC=0x8000` (ФИКС.)**,
banked code в W3. crt0_banked, как crt0_small, авто-детектит W2.
Помечено `[TODO]` — не обкатано.
- Отличие от small: small цепляет DATA сразу за CODE (`--data-loc 0`); huge
ФИКСИРУЕТ DATA на 0x8000.
### 4.2 ТРЕБОВАНИЕ (по пользователю): huge должен переносить DATA динамически
Сейчас huge жёстко кладёт DATA на 0x8000. Если РЕЗИДЕНТНЫЙ CODE вылезет за
0x8000 (W1 = только 0x4000..0x7FFF ≈ 16 КБ; резидент > 16 КБ лезет в W2) →
коллизия с DATA. **Надо научить huge класть DATA динамически ЗА резидентным
CODE (как small: `--data-loc 0` + crt0 считает старт), а не на фикс 0x8000.**
Тогда huge = «small-раскладка резидента (W1+W2, DATA за CODE) + ДОП. код в
банках W3». Это первый пункт работ по huge.
### 4.3 КОНФЛИКТ: графика тоже хочет W3 (ключевой риск)
`pop_banking_architecture` прямо говорит: **графику нельзя в W3** (блиты/атласы
используют окна; см. §4.5). Поэтому в банки W3 можно выносить ТОЛЬКО
НЕ-графические блоки, и такой банк НЕ должен во время своего исполнения держать
графику в W3. Если W3-банкованная функция ЗОВЁТ графику (которой нужен W3),
трамплин обязан сохранить/восстановить банк вокруг вызова (проверить, что
banking-ABI это делает — `sdcc_banking`). Альтернатива без этого риска —
**big + BANK_W1** (банк кода в W1, не W3), рекомендованная в
`pop_banking_architecture` именно из-за W3-графики. Сессия должна выбрать:
huge(W3) с аккуратным save/restore ИЛИ big(BANK_W1).
### 4.4 Какие блоки МОЖНО вынести (не работают с графикой напрямую)
Замер graphics-ref по модулям (grep `gfx_|blit|env_b|wall_b|fore_b|setfillstyle|
bar(|GFX_BANK|initgraph`):
```
pop_bg.c : 83 — РЕЗИДЕНТ (вся отрисовка)
roomtest.c : 23 — РЕЗИДЕНТ (главный цикл + флип страниц)
pop_level.c : 17 — использует gfx_w0_map (W0, не W3-блиты) — ПОГРАНИЧНЫЙ
pop_kid.c : 12 — kid_draw = графика; НО play_seq — чистая логика (можно split)
pop_ctrl.c : 0 — КАНДИДАТ В БАНК (ввод/диспетчер control)
pop_map.c : 0 — КАНДИДАТ В БАНК (коллизия/физика, ~3.8 КБ) — лучший по объёму
pop_trob.c : 0 — КАНДИДАТ В БАНК (кнопки/ворота/пики-логика)
```
- **Лучшие кандидаты в W3-банк(и): pop_map + pop_trob + pop_ctrl** (нет прямой
графики; вместе ~5.3 КБ CODE). Освобождают резидент → он влезает в W1.
- **Осторожно с межбанковыми вызовами:** pop_map/pop_trob ЗОВУТ pop_bg
(перерисовка loose/пик/кнопок/шва) и pop_kid (play_seq/kid_set_seq). Это
кросс-банк вызовы через трамплин (`sdcc_banking`: стек +3 байта, виртуальный
24-битный адрес). Правило `pop_banking_architecture`: «один файл = один банк
= прямые вызовы», main резидентен. Проверить, что трамплин сохраняет W3
вокруг вызова в графический pop_bg (см. §4.3).
- **pop_kid split** (по желанию): вынести play_seq/seqtbl-интерпретатор
(логика + таблицы kid_data.h) в банк, оставить kid_draw/kid_heal резидентными.
Даёт и −код, и −данные из резидента, но требует аккуратного разделения TU
(1 функция = 1 модуль, см. `libc_one_function_per_module`).
- **pop_level: пограничный** — не блитит, но маппит уровень в W0; банковать
можно, если W0-логика совместима с трамплином (проверить ISR-стаб взаимодействие).
### 4.5 Почему графику нельзя в W3 (контекст)
Блиттер держит спрайт-страницу атласа в **W0** (`_gfx_w0_state`,
`_gfx_w0_isr`). Ускоритель/адресация видео — отдельная тема (см.
`sprinter_accelerator`, `sprinter_graphics`). W3 в banked-раскладке — окно
кода-банка; смешивать с окном, которое графика перемапливает, нельзя без
save/restore. Детально — `pop_banking_architecture`, `graphics_constraints`.
---
## 5. РЕКОМЕНДУЕМЫЙ ПОРЯДОК РАБОТ (для след. сессии)
1. **Замер-базлайн** (CODE/DATA/BSS + per-module) — зафиксировать до.
2. **Путь A** дешёвые пункты (флаги, дедуп кнопки, дедуп y_to_row) — быстрый 1..2 КБ.
3. **huge §4.2**: научить huge класть DATA динамически (как small) — инфраструктурный
пререквизит, без него банкинг не даст гибкости. Обкатать в MAME на текущем
резиденте (пока без выноса — просто huge-раскладка = small + пустой W3).
4. **huge §4.4**: вынести pop_map (+pop_trob, +pop_ctrl) в W3-банк(и); проверить
кросс-банк вызовы в pop_bg (§4.3) в MAME. ЛИБО выбрать big+BANK_W1.
5. **Путь B** (по остатку нужды): прототип выноса `kid_data.h` в EMM-страницу
Kid с маппингом раз на кадр; замерить скорость (`sprite_engine_perf`).
Затем аудит draw-таблиц по W0-контексту (§3 a/b/c/d).
## 6. Ссылки
- `bin/sprinter-cc` (§162+ — резолв memory-mode → CODE_LOC/DATA_LOC).
- `runtime/crt0_small.*`, `runtime/crt0_banked.*`, `runtime/bank.s`.
- memory: `sprinter_memory_modes`, `memory_modes_implemented`,
`setwin2_for_w2_alloc`, `sdcc_banking`, `bank_local_data_pattern`,
`pop_banking_architecture`, `avoid_32bit_arith_z80`,
`libc_one_function_per_module`, `sprite_engine_perf`, `mdview2_size_budget`.
- `applications/PoP/roomtest/bug_list.md` — открытые баги Фазы B (не блокируют
оптимизацию, но держать в уме при рефакторе pop_map/pop_bg).
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -3
View File
@@ -6,8 +6,8 @@ EXAMPLE := roomtest
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY ?= small
EXTRA_FLAGS ?= --gfx 256
EXTRA_SRCS := pop_bg.c pop_kid.c pop_ctrl.c pop_map.c pop_level.c
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c --w3 pop_map.c --w3 pop_bg.c
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c
BG_DIR := $(CURDIR)/../poc/res/bg
KID_DIR := $(CURDIR)/../poc/res/kid
@@ -33,4 +33,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
kid_data.h: $(TC)/pop_extract_kid_data.py
cd $(TC) && python3 pop_extract_kid_data.py
$(EXAMPLE).exe: pop_bg.c pop_bg.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 kid_data.h $(BG_DATA) $(KID_DATA) $(LVL_DATA)
$(EXAMPLE).exe: pop_bg.c pop_bg.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) $(LVL_DATA)
+333
View File
@@ -0,0 +1,333 @@
# roomtest — список известных багов (Фаза B и смежное)
Найдено при тестировании Фазы B (кнопки/ворота) в MAME. **НЕ решаем сейчас**
— задача следующего этапа. Правило проекта: механику сверять с
`applications/PoP/SDLPoP/src/` ДО кодинга.
Статус на момент записи: 2026-07-21. Тестовый старт — комната 6 (`START_ROOM`
в `roomtest.c`). Кнопка room6(0,2, opener) → ворота room8(0,9), видны в левом
шве room6.
---
## BUG-1. Боковой переход через ворота: Kid проваливается на row 1 — **Critical**
**Симптом:** при проходе через ОТКРЫТЫЕ ворота в соседнюю комнату (через шов)
Kid оказывается на ряду **row 1** вместо row 0 — «проваливается» на этаж ниже
(стоит в дверном проёме на неправильной высоте).
**Где:** `pop_map.c` `check_leave()` (порт seg002 leave_room) — боковой уход
`pop_leave_dir`; репроекция координат `Kid.x ∓= 140` есть, но Y/`curr_row`
при боковом переходе НЕ репроецируются (в отличие от `check_leave_below`,
где `Kid.y -= 189; curr_row = y_to_row`). При входе в соседнюю комнату у
шва Kid, видимо, попадает в позицию, где `determine_col`/физика следующего
кадра сбрасывают его на row 1 (нет пола на row 0 у входа, либо неверный y).
**Сверить с SDLPoP:** `goto_other_room` (seg002) — как именно
пересчитываются `Char.curr_row`/`Char.y` при side-переходе (left/right).
Для вертикального перехода мы это сделали, для бокового — нет.
## BUG-2. Возврат из комнаты назад: Kid отбрасывается обратно (ping-pong) — **Critical**
**Симптом:** после перехода в соседнюю комнату попытка сразу вернуться
обратно приводит к тому, что Kid снова закидывается в ту же комнату —
переход «пинг-понгует», выйти нельзя.
**Где:** `pop_map.c` `check_leave()`. После перехода Kid репроецирован к
ПРОТИВОПОЛОЖНОЙ кромке новой комнаты (`x ∓ 140`). На СЛЕДУЮЩЕМ же кадре
передний край снова за порогом ухода в обратную сторону → `pop_leave_dir`
взводится опять → мгновенный обратный переход. Нужен guard «только что
перешёл» (подавить повторный leave на N кадров / пока Kid не отойдёт от
кромки), как в оригинале (`next_room`/leave обрабатываются раз за кадр +
Kid входит достаточно внутрь).
**Сверить с SDLPoP:** порядок `leave_room`/`goto_other_room` + на какой
X ставится персонаж при входе (должен быть НЕ на самой кромке, иначе
мгновенный обратный триггер).
## BUG-3. Climb-up на тайл-кнопку: неправильная окклюзия (Kid поверх кнопки) — **Critical**
**Симптом:** когда Kid подтягивается (climb-up) на тайл, верх которого —
КНОПКА (opener/closer), а не обычный пол, отрисовка неверная: Kid рисуется
ПОВЕРХ кнопки, хотя передняя грань пола/кнопки должна перекрывать нижнюю
часть Kid во время подъёма.
**Где:** `pop_bg.c` `climb_overlay_tile()` — floor-overlay при подъёме
рисуется только для floor-подобных тайлов `code ∈ {1,3,5,19}`. Кнопка
(opener 0x0F / closer 0x06) в этот набор НЕ входит → пол-оверлей кнопки не
рисуется поверх Kid. Нужно: для нажатой кнопки (draw как floor/stuck)
трактовать её как floor-тайл в climb-overlay (учесть подстановку из
`get_tile_to_draw`), и/или добавить opener/closer в набор.
**Сверить с SDLPoP:** `draw_floor_overlay` (seg008) + `get_tile_to_draw`
(нажатая кнопка → tiles_1_floor / tiles_5_stuck) — оверлей должен идти по
ПОДСТАВЛЕННОМУ типу тайла.
---
## Оптимизации (отложено)
### OPT-1. Хирургический редрой левого шва (ворота соседа) — 2026-07-22
**Возможность:** `pop_room_redraw_seam_left()` (pop_bg.c) на каждое изменение
openness рисует `bar(BLACK)` по всему col0 + **полный `draw_tile(0,0)`**
(стены, `topright`, `wall_pattern` с `prandom()` — десятки блитов). Реально
анимируются только бары решётки — `draw_gate_back` (~9 `env_b`).
**Стоимость:** seam-блок (синий io_border) занимает ~30-50% кадрового периода,
**но только пока openness меняется** — т.е. во время открытия и медленного
авто-закрытия (~5 сек после схода с кнопки). В покое (ворота открыты-держим
кнопку / закрыты / FF) — 0%. Замерено в MAME: брейк на
`_pop_room_redraw_seam_left` (0x5A54) срабатывает ⟺ сегмент дорогой.
**Приём** (как у пик, но сложнее — heal НЕ годится: печёные бары устаревшие,
поэтому и стоит `bar(BLACK)`+redraw): `bar(BLACK)` только по полосе баров
(x=0, gate_top..gate_bot) + `draw_gate_back(lmod,...)` + дорисовать статику
тайла (0,0), задетую полосой. Ожидаемо срезает бóльшую часть 30-50%.
**Пиксель-чувствительно** — полоса баров пересекается со статикой col0,
обязательна выверка в MAME по кромкам.
**Решение (2026-07-22):** оставляем как есть — стоимость транзиентная, в бюджет
помещаемся. Делать, если упрёмся в кадровый бюджет на сценах с воротами.
---
## Исправлено
- **Шов ворот жёг 50% кадра в покое (закрытая решётка)** — ИСПРАВЛЕНО
2026-07-22. Причина — **баг кодогенератора SDCC z80** (memory
`sdcc_z80_cmp_store_a_bug`): `if (m[9] != seam_sig) seam_sig = m[9];`
компилировался в `sub (seam_sig)` (A ← разность) + `ld (seam_sig),a`
сохранял РАЗНОСТЬ `m[9]-seam_sig`, не `m[9]`. `seam_sig` осциллировала
(напр. 25↔231), `m[9]!=seam_sig` истинно каждый кадр → `draw_tile(0,0)`
каждый кадр даже у неподвижной решётки. Фикс: store-до-сравнения
(`seam_sig = g;` из чистого `g` ДО `sub`), подтверждён в .asm. Прочёс всех
модулей PoP: других случайных compare-then-store нет.
- **Пики: 2×полный `draw_tile` на кадр анимации** → хирургический редрой,
2026-07-22. `pop_spike_redraw`: `heal_off` уже возвращает всю печёную
статику (база 127, пол, грани); поверх анимируются только два острия
(`SPIKES_FRAM_LEFT` в своей ячейке + `SPIKES_FRAM_RIGHT` в соседней).
Заменили 2×`draw_tile` на 2×`env_b` — пиксель-в-пиксель тот же результат,
в разы дешевле (шахта из нескольких пик больше не съедает полный кадр).
- **Отрисовка нажатой кнопки (0,2)/(0,3)** — ИСПРАВЛЕНО. Причина: `fore_tile`
(pop_bg.c, fore-слой поверх Kid) рисовал переднюю грань КНОПКИ (bottom_id
149) поверх уже нарисованной грани пола (43) — «остаток нажатой кнопки».
Фикс: `fore_tile` применяет ту же подстановку нажатой кнопки, что и
`draw_tile` (opener→floor / closer→stuck при таймере связи >1). Плюс
`pop_button_redraw` — wipe своей ячейки + правой грани (дальний угол в
0,3), низ строго yb+64 (не залезать в стену ряда 1).
## Заметки
- Тестовые клавиши осторожного шага: **J** = шаг влево, **L** = шаг вправо
(эмуляция Shift+стрелка), см. `pop_ctrl.c` `KBD_DBG_STEP*`. Первый шаг в
сторону = разворот (как в оригинале safe_step), движение со второго.
- Респавн после смерти — по **↑** (или авто через `RESPAWN_DELAY`).
- `START_ROOM=6` — временный тестовый старт; вернуть на
`pop_level_start_room()` после отладки объектов.
---
## BUG-CEIL-1. Прыжок вверх: руки Kid рисуются ПОВЕРХ потолка — **Medium**
**Симптом (2026-07-22):** при прыжке вверх (SEQ up, кадры 67..79) руки/голова
Kid заходят в полосу кладки у потолка (row -1) и рисуются ПОВЕРХ неё — потолок
должен окклюдить руки (как передняя грань).
**Где:** `pop_bg.c` — полоса потолка (`draw_tile(-1)`) сейчас только фон
(запечена в pop_room_draw), не окклюдер поверх Kid. Нужен ceiling-overlay в
`pop_fore_over_kid` (перерисовать срез полосы над Kid поверх спрайта), по
аналогии с fore-тайлами. Инфраструктура `g_clip_top`/`gfx_blit_part` уже есть.
## BUG-CEIL-2. Тряска/разбитие loose-плиты в потолке (row -1) не работает — **Medium**
**Симптом (2026-07-22):** loose-плита в ряду 2 верхнего соседа (room5 (2,5) →
потолок room6 над (0,5)) при прыжках Kid на (0,5) НЕ трясётся и не разбивается.
**Где:** верхний сосед не «живой» — `above_fg` берётся из статики уровня,
loose-состояние соседней комнаты не тянем (per-room loose/modif не персистятся,
P0). check_press/knock работают только по текущей комнате. Нужно: (а) пробой
loose сверху через границу комнаты, (б) персистентное loose-состояние соседа +
его отрисовка в полосе. Пока полоса рисует loose статически (кадр покоя).
## BUG-OCCL-1. Тень дальней колонны перекрывает Kid в (0,4)-(0,5) — **Medium**
**Симптом (2026-07-22):** Kid, стоя/прыгая у (0,4)-(0,5), частично перекрыт
тёмной штриховкой — это ТЕНЬ/боковая грань ДАЛЬНЕЙ колонны (сзади по глубине),
которая НЕ должна окклюдить персонажа. Over-occlusion fore-слоя.
**Где:** `pop_bg.c` `pop_fore_over_kid`/`fore_tile` — рисует fore футпринт-тайлов
без учёта глубины (какая грань РЕАЛЬНО перед Kid). Порт seg003 redraw_at_char
окклюдит только передние грани; дальняя колонна (за Kid) не должна. Сверить с
SDLPoP redraw_at_char (какие тайлы/грани идут в fore vs back по y-сортировке).
## BUG-CEIL-3. Анимация ворот стирает потолок над ними — **Medium**
**Симптом (2026-07-22):** при опускании/поднятии решётки шва (анимация openness)
полоса кладки у потолка НАД воротами пропадает (см. потолок над левым швом).
**Где:** `pop_bg.c` `pop_room_redraw_seam_left``pop_button_redraw`/
`pop_spike_redraw` — общий класс): чёрный `bar` по col0 row0 стирает потолочную
полосу (row-1), а redraw рисует только сам гейт, не восстанавливая полосу.
Нужно: при перерисовке шва/тайла у потолка перерисовать и срез потолочной полосы
(draw_tile(-1,col) с g_clip_top) над затронутыми колонками.
---
# BUG-SEAM-PINGPONG (#4). Пинг-понг drawn_room у шва с закрытыми воротами — **РЕШЁН 2026-07-22**
Статус: 2026-07-22, вечер. НАСТОЯЩИЙ корень найден потиковой трассой ЖИВОГО
SDLPoP 1.23 (lldb-брейкпоинты на leave_room/bumped/safe_step с логом Char +
char_x_left/right; fixes выключены = vanilla). Все прежние гипотезы ниже
оставлены для истории — они НЕ были причиной.
## КОРЕНЬ (подтверждён трассой + исходником)
`set_char_collision` (seg006:0723): `char_x_right = obj_x/2 + 58`, где
`load_frame_to_obj` (seg008:1728) считает `obj_x = 2*char_dx_forward(dx) - 116`
и **добавляет +1** для кадров «чётного пикселя»:
`if ((sbyte)(cur_frame.flags ^ obj_direction) >= 0) ++obj_x;`
(бит 0x80 флагов кадра XOR направление; вправо: +1 если бит НЕ стоит).
Деление `obj_x/2` — C-усечение К НУЛЮ, поэтому при `e = x+dx <= 57`
(obj_x < 0, зона левого шва) поправка +1 даёт `char_x_right = e+1`, а при
e >= 58 формула сокращается к чистому `e`.
Итог: Kid, осевший после отскока от ворот шва на x=57 (frame15, флаги 0x43 —
бит 0x80 не стоит), имеет **char_x_right = 58** и порога leave-left (<=57)
НЕ достигает. Наш движок считал передний край как `Kid.x + dx` без поправки
→ 57 → ложный leave → пинг-понг.
Эталонный цикл SDLPoP (сн normalized по трассе): стойка x=61 → тап вправо →
safe_step(d=0) → step, на первом dx(1) x=62 → bump (edge-триггер) → align 61 →
seq47 dx(-4) → **x=57** (скрыт за кромкой) → кадры 50/51/52 (cxr 61/60/58, у
всех бит 0x80 снят, e>57 — без сдвига) → стойка cxr=58 → leave НЕ срабатывает;
тап → safe_step d=3 → x=60 (1/3 видно); тап → step1 → x=61 (2/3 видно); тап →
bump → 57 … по кругу. Char.room и drawn_room НЕ меняются вообще.
## Фикс (pop_map.c)
`char_x_forward_edge()`: `e = char_dx_forward(dx); if (((flags ^ (dir<0 ?
0x80 : 0)) & 0x80) == 0 && e <= 57) e++;` — используется в `char_front_coll`
(коллизия/bump/edge_distance) и в `check_leave` (порог ухода). Плюс порт
doortop-гарда leave-right из leave_room (тайл (9,row) = doortop → правого
выхода нет). `pop_leave_timer` (exit_room_timer) оставлен — он реален в
seg002/seg003.
## Гипотезы, ОТПАВШИЕ после трассы (история диагноза)
## Симптом (подтверждён пользователем + инструментально)
Kid стоит за решёткой закрытых ворот шва (левый сосед room8 виден в кромке
room6). При удержании/нажатии ВПРАВО экран пинг-понгует между двумя
состояниями:
- **A**: показывается room6, Kid у левой кромки за решёткой (спрайт виден на 2/3);
- **B**: показывается room8, Kid у его правой кромки.
Эталон SDLPoP (описан пользователем): drawn_room **всегда остаётся room6**,
Kid осциллирует у кромки (1/3→2/3→отступил→по кругу), в room8 экран НЕ
переключается. То есть у нас лишний свитч A→B.
## Тестовый контекст
START_ROOM=6. Ворота room8(0,9) закрыты, видны в col0 room6. Kid-структура
kframe: см. pop_kid.h (frame,x,y,direction,curr_col,curr_row,action,...).
Адрес _Kid и статиков — в .sprinter-cc-roomtest/roomtest.map (сдвигается от
сборки к сборке). MAME: memory `mame_mcp_bridge`/`mame_hdd_test_disk`.
Отладочные: J/L — короткий шаг влево/вправо (эмуляция Shift+стрелка).
## Инструментальный диагноз (watchpoint на pop_leave_dir)
В момент лишнего свитча A→B: `pop_leave_dir=1 (LEFT)`, Kid **frame=15 (СТОЯ,
не transient!), Kid.x=57** (до репроекции +140). То есть:
- `char_x_right = char_dx_forward(kid_cur_dx()) = Kid.x + frame15.dx = 57+0 = 57`.
- Порог leave-left (взгляд вправо): `char_x_right <= 57` → срабатывает РОВНО на 57.
- Грань ворот (где их держит коллизия) = **61** (`wall_dist_from_left[1]=10 +
coll_tile_left_xpos=51`). Между 57 и 61 — **зазор 4px**: Kid НЕ удержан
воротами (d=6157=4≥0 → check_bumped не бампит), но уже на пороге ухода.
- Kid оседает на 57 из-за recoil отскока: seq_47 = `act(bumped), dx(-4),
frame_50, 51, 52`; SEQ_DX(4) двигает Char.x на −4 суммарно; frame_50.dx=4
компенсирует ТОЛЬКО точку коллизии НА кадре 50, но при возврате в стойку
(frame15, dx=0) `char_x_right = Char.x = aligned4 = 57`.
## Что ИСКЛЮЧЕНО (сверено с исходниками SDLPoP, НЕ причина)
- Формула char_x: `char_x_right = obj_x/2+58 = Char.x+frame.dx` (seg006
set_char_collision) — совпадает с нашим char_dx_forward.
- Позиция грани ворот: `get_left_wall_xpos = wall_dist_from_left[1](10) +
xpos_in_drawn_room(x_bump[9+5])+7 = 10+(184140)+7 = 61` — совпадает с нашим
(`x_bump[1+5]=44`, +7, +10 = 61).
- Порог leave: SDLPoP leave_room looking-right `char_x_right<=57` — совпадает.
- Данные кадров: frame_50 (image=49,dx=4,flags=0x67) и frame_15
(image=14,dx=0,flags=0x43,sword=9) — БАЙТ-В-БАЙТ как в SDLPoP frame_table_kid.
- seq_47 (act bumped, dx(-4), frame 50/51/52) — совпадает.
## Почему точечные фиксы НЕ работают
- Гард в check_leave (подавить leave на закрытых воротах) — это ОТСЕБЯТИНА,
не SDLPoP (в leave_room такого нет); откачено.
- `exit_room_timer=2` (порт seg002 exit_room — РЕАЛЬНЫЙ механизм, оставлен как
pop_leave_timer): блокирует leave 2 кадра после входа в комнату. НЕ спасает:
положение Char.x=57 **устойчивое** (Kid стоит), а не transient — после
истечения таймера leave срабатывает.
- По коду SDLPoP raw-рассуждением раз за разом выходит, что leave-left ДОЛЖЕН
сработать и там (Char.x=57), но эталон показывает, что нет. Значит различие
не в этих кусках, а в ДИНАМИКЕ: где именно оседает Char.x у шва И как
вычисляется drawn_room.
## КОРЕНЬ (гипотеза)
Наш движок держит `drawn_room == комната_Kid` ВСЕГДА, а шов подделан:
- коллизия — самодельные снапшоты кромок соседа (g_lcol/g_rcol, 1 колонка) +
`x_bump[tcol=-1]`, а НЕ порт `curr_row_coll_room[]`/`get_all_coll_row`/
`xpos_in_drawn_room` из seg004;
- уход — мгновенный `enter_room(сосед)` при пороге char_x, а НЕ отложенный
drawn_room через `next_room`/`check_the_end`.
Из-за упрощённой коллизии Char.x у шва оседает иначе, чем в SDLPoP (у нас ровно
57 = порог), и/или drawn_room переключается там, где в оригинале нет.
## План S2/S3 (порт coll_room + отложенный drawn_room) — НЕ потребовался для
## этого бага (трасса показала: в эталоне Char.room/drawn_room вообще не
## меняются). Остаётся как задел под стражей/двух персонажей в кадре.
Правило проекта: SDLPoP — источник истины, переводить 1:1, НЕ рассуждать «как
должно быть». Нужно портировать связку целиком:
1. **Раздельные комнаты.** `Char.room` (реальная комната персонажа) ≠
`drawn_room` (отрисованная). У нас уже есть `kid_room` (S1) + рендер-смещение
`pop_kid_set_render_dx(∓140)` (порт xpos_in_drawn_room для СПРАЙТА).
2. **Коллизия по Char.room через coll_room (seg004, S2).** Портировать
`check_collisions` → `get_row_collision_data` (строки curr/below/above) →
`get_left_wall_xpos`/`get_right_wall_xpos` (по `xpos_in_drawn_room(x_bump[
col+FIRST_ONSCREEN_COLUMN])`, ±140 когда колонка принадлежит соседу) →
массивы `curr_row_coll_room[]`/`_flags[]`, `bump_col_left_of_wall`/
`bump_col_right_of_wall` → `check_bumped_look_left/right` → `bumped()`.
Тайлы берутся из РЕАЛЬНОЙ комнаты колонки (get_tile(room,col,row) для любой
комнаты), НЕ из снапшота. Именно это определяет, где Char.x оседает у шва.
3. **Отрисовка по drawn_room, персонаж со сдвигом.** Рисуем drawn_room; Kid
(и будущие стражи) — со смещением ±140, если их комната ≠ drawn_room.
4. **Отложенная смена drawn_room (seg002/seg000, S3).** Уход =
`leave_room` (seg002:0504, ПОРТ ЦЕЛИКОМ, включая up/down/side, guard'ы
climb/standup/turn, doortop-check для right) → `goto_other_room`
(seg002:0486: Char.room=сосед, x±140/y±189). `exit_room` (seg002:03C7):
`exit_room_timer` + `next_room = Char.room`. `check_the_end` (seg000:0FBD):
`if (next_room != 0 && next_room != drawn_room) drawn_room = next_room;
redraw`. drawn_room меняется ТОЛЬКО так, а не по нашему прямому enter_room.
Ключевой момент, который надо переводить БУКВАЛЬНО (а не выводить): порядок
вызовов в play_frame (seg000:863 `play_frame`: … play_kid_frame → exit_room →
check_the_end) + set_char_collision перед leave_room в exit_room.
Карта сегментов: seg002 (leave/goto/exit_room), seg004 (коллизия/coll_room/
bumped), seg006 (set_char_collision/play_seq/frame_table_kid), seg000
(play_frame/check_the_end/draw). Наши файлы: pop_map.c (коллизия/физика/leave),
roomtest.c (enter_room/drawn_room/главный цикл), pop_kid.c (кадры/рендер-сдвиг).
Реализовано на 2026-07-22: S1 (kid_room + render_dx) + pop_leave_timer.
S2/S3 отложены — баг закрыт odd-pixel поправкой (см. КОРЕНЬ выше).
Память: [[pop_seam_room_model]], [[sdlpop_odd_pixel_char_x]].
+181 -21
View File
@@ -14,6 +14,7 @@
#include <fcntl.h>
#include <unistd.h>
#include "pop_bg.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
/* ---- Атласы (прямая адресация) ------------------------------------- */
static atlas_t env[5];
@@ -44,20 +45,36 @@ void pop_bg_free(void)
atlas_free(&fore_at);
}
/* Верхний клип блитов фона (ЭКРАННАЯ y): !=0 — строки кадра с screen_y <
* g_clip_top не рисуются (частичным блитом со сдвигом источника). Нужно
* полосе кладки у потолка (row<0): высокие кадры не должны лезть в верхний
* борт (за пределы игрового поля). 0 — без клипа. */
static int g_clip_top;
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0). */
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0).
* При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{
const uint8_t *img;
uint16_t w, h;
int top;
if (idx >= a->count)
return;
img = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page);
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 + POP_YOFF, img); /* +YOFF: центрирование */
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
if (w && h) {
if (g_clip_top && top < g_clip_top) {
int sy = g_clip_top - top; /* пропустить верхние строки */
if (sy < (int)h)
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy);
} else {
gfx_blit(x, top, img);
}
}
gfx_w0_unmap();
}
@@ -116,6 +133,8 @@ static const uint8_t WALL_FRAM_BOTTOM[4] = {7, 9, 5, 3};
static const uint8_t WALL_FRAM_MAIN[4] = {8, 10, 6, 4};
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_LEFT[10] = {0, 128, 129, 130, 131, 132, 131, 129, 128, 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};
@@ -133,6 +152,7 @@ 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 const uint8_t *g_above, *g_abovemod; /* row2 fg/mod комнаты СВЕРХУ (полоса кладки у потолка) */
static uint8_t g_room;
/* Задать ряд 0 комнаты снизу (fg-коды) — нужен для draw_tile_topright/
@@ -141,13 +161,22 @@ static uint8_t g_room;
* снизу (кромка уровня = стена). */
void pop_room_set_below(const uint8_t *below_row0_fg) { g_below = below_row0_fg; }
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod)
{ g_above = above_row2_fg; g_abovemod = above_row2_mod; }
static uint8_t tile_code(int row, int col)
{
if (col < 0) {
if (g_lfg && row >= 0 && row < 3) return g_lfg[row];
return 20; /* уровневая кромка = стена */
}
if (row < 0) return 1; /* верхняя кромка = пол */
if (row < 0) { /* верхняя полоса: ряд 2 комнаты сверху */
if (row == -1 && g_above && col < 10) return g_above[col];
return 1; /* нет комнаты сверху → пол (кромка) */
}
if (row > 2 || col > 9) return 20;
return g_fg[row * 10 + col];
}
@@ -158,6 +187,7 @@ static uint8_t tile_mod(int row, int col)
if (g_lbg && row >= 0 && row < 3) return g_lbg[row];
return 0;
}
if (row == -1 && g_abovemod && col < 10) return g_abovemod[col];
if (row < 0 || row > 2 || col > 9) return 0;
return g_bg[row * 10 + col];
}
@@ -213,7 +243,9 @@ static void wall_pattern(int row, int col, int which_part)
uint16_t mid_div, mid_off, bot_div, bot_off;
uint8_t bg;
rnd_seed = (unsigned long)(g_room + TBL_LINE[row] + col);
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
prandom(1); /* сброс */
mid_div = prandom(1);
mid_off = prandom(4);
@@ -321,6 +353,14 @@ static void draw_tile(int row, int col)
uint8_t code = tile_code(row, col);
uint8_t lcode = tile_code(row, col - 1);
uint8_t lmod = 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(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
else if (code == 0x06) { if ((pop_doorlink2(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 = COL_XH[col];
int x = xh * 8;
int dby = 63 * row + 65;
@@ -367,15 +407,19 @@ 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: правая грань (анимир. по 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])
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
/* --- 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) */
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])
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
}
}
/* --- draw_tile_bottom (seg008.c:570) --- */
@@ -387,12 +431,16 @@ static void draw_tile(int row, int col)
env_b(t->bottom_id, x, dby);
}
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif) --- */
/* --- draw_loose (seg008.c:600) — нижняя грань (анимир. по modif). В
* верхней полосе (row<0) loose ряда 2 верхнего соседа рисуем статически
* (кадр покоя 43): анимацию/разбитость соседа мы не тянем. --- */
if (code == 11)
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])], x, dby);
env_b(row >= 0 ? LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])]
: LOOSE_FRAM_BOTTOM[0], x, dby);
/* --- draw_tile_base (seg008.c:611) --- */
{
/* --- 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 = LOOSE_FRAM_LEFT[get_loose_frame(pop_loose_modif[row * 10 + col])];
@@ -400,6 +448,17 @@ static void draw_tile(int row, int col)
env_b(base_id, x, t->base_y + dmy);
}
/* --- draw_tile_anim (seg008.c:0B2B) — левый кадр выдвинутых пик в
* СВОЕЙ ячейке (анимир. по своему modif). Без него остриё пики
* рисуется только правым соседом со сдвигом → шахта над самой пикой
* пустая. */
if (row >= 0 && code == 2) {
uint8_t sm = tile_mod(row, col);
uint8_t sf = (sm & 0x80) ? 5 : sm;
if (sf > 0 && sf < 10 && SPIKES_FRAM_LEFT[sf])
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
}
/* --- draw_tile_fore (seg008.c:690) --- */
if (code == 20) {
wmod = wall_modifier(row, col);
@@ -431,6 +490,75 @@ void pop_loose_shake_draw(int row, int col)
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Перерисовать пики тайла (row,col) на back-странице по ЖИВОМУ modif
* (pop_trob). Кадр выдвижения рисует ПРАВЫЙ сосед (draw_tile ветка lcode==2
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
void pop_spike_redraw(int row, int col)
{
int x = COL_XH[col] * 8;
int dmy = 63 * row + 62; /* как в draw_tile (dby-3) */
uint8_t sm = g_bg[row * 10 + col]; /* живой modif пики (room_modif) */
uint8_t sf = (sm & 0x80) ? 5 : sm; /* видимый кадр (hold 0x80..0x8F → 5) */
/* Шипы выдвигаются выше линии пола (env_b на dmy-7) — heal с запасом
* вверх (63*row+34 .. +74) покрывает и остриё, и сам пол; ширина 64 =
* тайл + правый сосед (остриё рисуется в его ячейке).
* heal вернул из ОЗУ-копии ВЕСЬ печёный статичный фон (база пик 127, пол,
* грани соседей — всё, что рисовал бы полный draw_tile при modif=0).
* Единственное, что реально анимируется — два спрайта острия (LEFT в своей
* ячейке + RIGHT в соседней, оба по sf). Поэтому вместо 2×draw_tile
* (десятки блитов статики поверх идентичного фона = идемпотентно) рисуем
* РОВНО эти два острия — пиксель-в-пиксель тот же результат, во много раз
* дешевле (порт redraw_21h: draw_tile_anim + draw_tile_anim_right). */
heal_off(x, 63 * row + 34, 64, 40);
gfx_set_bank(GFX_BANK_SPRITE);
if (sf > 0 && sf < 10) {
if (SPIKES_FRAM_LEFT[sf]) /* остриё в своей ячейке */
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
if (col + 1 < 10 && SPIKES_FRAM_RIGHT[sf]) /* остриё в ячейке соседа */
env_b(SPIKES_FRAM_RIGHT[sf], COL_XH[col + 1] * 8, dmy - 7);
}
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Перерисовать ЛЕВЫЙ шов (col0) на back-странице по живому modif соседа —
* ворота соседней комнаты слева (portcullis room8) видны в col0 текущей
* комнаты через draw_gate_back (draw_tile ветка lcode==4, lmod = g_lbg[0]).
* g_lbg (lcol_bg) должен указывать на живой modif (roomtest обновляет из
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
void pop_room_redraw_seam_left(void)
{
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(0, POP_YOFF, 31, POP_YOFF + 64); /* col0, ряд 0 (бары ворот) */
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(0, 0); /* фон col0 + бары ворот (живой modif) */
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Перерисовать тайл КНОПКИ (row,col) на back-странице: нажатая/отжатая
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_button_redraw(int row, int col)
{
int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row;
/* Wipe СВОЕЙ ячейки + правой грани кнопки (заходит в 0,3) + перерисовка
* пола. НИЗ строго yb+64 (≈ низ передней грани пола): ниже — СТЕНА ряда 1
* (высокий спрайт), которую тут не перерисовываем → иначе чёрный бар. */
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yb + 26, x + 59, yb + 64);
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 (текущая страница). Порт
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
@@ -571,6 +699,14 @@ void pop_room_draw(uint8_t room_num,
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) рисует ТОЛЬКО срез у потолка, без зачернения борта. */
g_clip_top = POP_YOFF;
for (col = 0; col < 10; col++)
draw_tile(-1, col);
g_clip_top = 0;
gfx_set_bank(GFX_BANK_NORMAL);
}
@@ -598,7 +734,13 @@ static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=
static void fore_tile(int row, int col) /* передний слой одного тайла */
{
uint8_t code = tile_code(row, col);
const piece *t = &tile_table[code];
const piece *t;
/* Нажатая кнопка → пол/stuck (как get_tile_to_draw), ИНАЧЕ fore рисует
* переднюю грань КНОПКИ (bottom_id 149) поверх Kid, перекрывая уже
* нарисованную грань пола 43 → «остаток нажатой кнопки» на грани. */
if (code == 0x0F) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 1; }
else if (code == 0x06) { if ((pop_doorlink2(tile_mod(row, col)) & 0x1F) > 1) code = 5; }
t = &tile_table[code];
int x = COL_XH[col] * 8;
int dmy = 63 * row + 62;
int dby = dmy + 3; /* = 63*row+65 (draw_tile_bottom) */
@@ -611,6 +753,12 @@ static void fore_tile(int row, int col) /* передний слой одно
} else {
if (t->bottom_id) /* передняя кромка пола (draw_tile_bottom) */
env_b(t->bottom_id, x, dby);
if (code == 2) { /* пики: fore-кадр поверх Kid (напоротый) */
uint8_t sm = tile_mod(row, col);
uint8_t sf = (sm & 0x80) ? 5 : sm;
if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf])
env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2);
}
if (t->fore_id) /* передние объекты: колонны/ворота/щебень */
fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy);
}
@@ -665,10 +813,11 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int wh = (int)((w + 1) >> 1); /* char_width_half */
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
int xr;
int8_t cL, cR, rT, rB, r, c;
int8_t cL, cR, cLraw, rT, rB, r, c;
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh;
cL = col_from_x(xl); if (cL < 0) cL = 0; else if (cL > 9) cL = 9;
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
cL = cLraw; if (cL < 0) cL = 0; else if (cL > 9) cL = 9;
cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9;
/* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y),
* причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе
@@ -695,6 +844,17 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
for (r = rT; r <= rB; r++)
for (c = cL; c <= cR; c++)
fore_tile(r, c);
/* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за
* ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) —
* перерисовать её бары ПОВЕРХ Kid. Иначе Kid, стоя за закрытыми воротами
* (col-1), рисуется поверх решётки. Бары = draw_gate_back по живому modif
* соседа (g_lbg = lcol_bg, openness). */
if (cLraw < 0 && g_lfg)
for (r = rT; r <= rB; r++)
if ((g_lfg[r] & 0x1F) == 4) {
int dby = 63 * r + 65;
draw_gate_back(g_lbg ? g_lbg[r] : 0, COL_XH[0], dby, dby - 3);
}
if (frame >= 137 && frame <= 144) { /* подъём: пол назначения поверх Kid */
uint8_t fidx = (uint8_t)(frame - 137);
for (r = rT; r <= rB; r++)
+18
View File
@@ -46,6 +46,11 @@ void pop_room_draw(uint8_t room_num,
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
void pop_room_set_below(const uint8_t *below_row0_fg);
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
* кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет
* комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod);
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
@@ -60,6 +65,19 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
void pop_loose_shake_draw(int row, int col);
void pop_loose_bake_empty(int row, int col);
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
void pop_spike_redraw(int row, int col);
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
void pop_room_redraw_seam_left(void);
/* Кнопка (opener/closer) — перерисовать тайл (row,col) на back-странице:
* нажата/отжата по таймеру связи. Звать из pop_process_trobs. */
void pop_button_redraw(int row, int col);
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
+12 -1
View File
@@ -20,6 +20,12 @@
#include "pop_kid.h"
#include "pop_map.h"
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
* scancodes. TODO: убрать после отладки. */
#define KBD_DBG_STEPL 0x3B /* J */
#define KBD_DBG_STEPR 0x4B /* L */
/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
#define CONTROL_RELEASED 0
#define CONTROL_IGNORE 1
@@ -314,9 +320,14 @@ static void read_input(void)
uint8_t r = kbd_raw_down(KBD_RIGHT);
uint8_t u = kbd_raw_down(KBD_UP);
uint8_t d = kbd_raw_down(KBD_DOWN);
/* Отладочный осторожный шаг: J/L = Shift+влево/вправо (см. KBD_DBG_STEP*). */
uint8_t sl = kbd_raw_down(KBD_DBG_STEPL);
uint8_t sr = kbd_raw_down(KBD_DBG_STEPR);
if (sl) l = 1;
if (sr) r = 1;
control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED);
control_y = d ? CONTROL_HELD_DOWN : (u ? CONTROL_HELD_UP : CONTROL_RELEASED);
control_shift = (kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
control_shift = (sl || sr || kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
? CONTROL_HELD : CONTROL_RELEASED;
}
+8 -1
View File
@@ -185,6 +185,10 @@ static uint8_t kid_lvalid[2];
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
static int kid_fp_x, kid_fp_y;
static uint16_t kid_fp_w, kid_fp_h;
/* Рендер-смещение по ЛОГИЧЕСКОЙ X (straddle у шва, порт xpos_in_drawn_room):
* ∓140 когда комната Kid ≠ отрисованной; 0 обычно. */
static int kid_render_dx;
void pop_kid_set_render_dx(int dx) { kid_render_dx = dx; }
int kid_fp_obj_x(void) { return kid_fp_x; }
int kid_fp_obj_y(void) { return kid_fp_y; }
uint16_t kid_fp_width(void) { return kid_fp_w; }
@@ -209,7 +213,10 @@ void kid_draw(void)
if (fr.image == 255)
return; /* пустой кадр */
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx);
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
* отрисованной — спрайт за кромкой. Коллизия (Kid.x в pop_map) НЕ
* смещается: она в кадре комнаты Kid; смещение только рендерное. */
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx) + kid_render_dx;
obj_x = (fwd << 1) - 116;
obj_y = (int)Kid.y + fr.dy;
if ((int8_t)(fr.flags ^ Kid.direction) >= 0)
+8
View File
@@ -56,6 +56,14 @@ uint8_t kid_cur_flags(void);
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
void kid_set_seq(uint8_t seq_id);
/* Рендер-смещение Kid по ЛОГИЧЕСКОЙ X (порт xpos_in_drawn_room, seg004):
* когда комната Kid ≠ отрисованной (straddle у шва), спрайт рисуется со
* сдвигом ∓140 (левый сосед −140 / правый +140), при этом Kid.x/коллизия
* остаются в кадре комнаты Kid. 0 — комнаты совпадают (обычный случай).
* Влияет на kid_draw и метрики футпринта (fore) — heal берёт запомненный
* прямоугольник, поэтому согласован автоматически. */
void pop_kid_set_render_dx(int dx);
/* Нарисовать текущий кадр Kid (банк GFX_BANK_SPRITE — видео-ОЗУ, без
* теневой копии; запоминает прямоугольник для kid_heal). */
void kid_draw(void);
+53
View File
@@ -20,12 +20,16 @@ extern void _gfx_w0_isr(void);
/* Смещения блоков blueprnt относительно начала данных (Table 6). */
#define BP_FG 0u
#define BP_BG 720u
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
#define BP_LINKS 1952u
#define BP_START 2112u
#define ROOM_TILES 30u
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
static uint8_t lvl_blk, lvl_page, lvl_ok;
/* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */
static uint8_t dl1[256], dl2[256];
int pop_level_load(const char *path)
{
@@ -47,11 +51,17 @@ int pop_level_load(const char *path)
/* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */
n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF);
if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */
uint16_t i;
stub = (uint16_t)&_gfx_w0_isr;
pg[0x38] = 0xC3; /* JP _gfx_w0_isr */
pg[0x39] = (uint8_t)(stub & 0xFF);
pg[0x3A] = (uint8_t)(stub >> 8);
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */
/* Скопировать таблицы дверных связей в W2 (страница ещё в W3). */
for (i = 0; i < 256; i++) {
dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i];
dl2[i] = pg[LVL_DATA_OFF + BP_LINKMAP + i];
}
}
sprinter_page_w3(saved_w3);
close(fd);
@@ -154,6 +164,49 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col)
return res;
}
uint8_t pop_doorlink1(uint8_t i) { return dl1[i]; }
uint8_t pop_doorlink2(uint8_t i) { return dl2[i]; }
void pop_doorlink2_set(uint8_t i, uint8_t v) { dl2[i] = v; }
uint8_t pop_level_tile(uint8_t room, uint8_t tilepos)
{
uint8_t v;
if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0;
gfx_w0_map(lvl_page);
v = (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F);
gfx_w0_unmap();
return v;
}
/* Батч-доступ: смаппить страницу уровня в W0 ОДИН раз, прочитать много
* тайлов через pop_level_tile_raw, размаппить один раз. Экономит N свапов
* окна 0 в горячих циклах (pop_process_trobs звал pop_level_tile на каждый
* trob каждый кадр). Между begin/end НЕЛЬЗЯ рисовать/звать код, которому
* нужно окно 0 в исходном состоянии — только сырые чтения уровня. */
void pop_level_access_begin(void) { if (lvl_ok) gfx_w0_map(lvl_page); }
void pop_level_access_end(void) { if (lvl_ok) gfx_w0_unmap(); }
uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos)
{
if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0;
return (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F);
}
void pop_level_room_bg(uint8_t room, uint8_t *out30)
{
const uint8_t *rbg;
uint8_t i;
if (!out30) return;
if (!lvl_ok || room < 1 || room > 24) {
for (i = 0; i < ROOM_TILES; i++) out30[i] = 0;
return;
}
gfx_w0_map(lvl_page);
rbg = room_bg_ptr(room);
for (i = 0; i < ROOM_TILES; i++) out30[i] = rbg[i];
gfx_w0_unmap();
}
uint8_t pop_room_link(uint8_t room, uint8_t side)
{
uint8_t v;
+23
View File
@@ -41,6 +41,29 @@ int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg,
* 0, если стороны нет. Для переходов между комнатами (L2/L3). */
uint8_t pop_room_link(uint8_t room, uint8_t side);
/* Тайл-код (fg & 0x1F) по комнате (1..24) и позиции tilepos (0..29,
* row*10+col); вне диапазона → 0 (empty). Для trob-диспетчера (pop_trob):
* тип анимируемого тайла в ЛЮБОЙ комнате, не только текущей. */
uint8_t pop_level_tile(uint8_t room, uint8_t tilepos);
/* Батч-доступ к странице уровня (один map/unmap окна 0 на много чтений).
* Между begin/end — только pop_level_tile_raw, без рисования. */
void pop_level_access_begin(void);
void pop_level_access_end(void);
uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos);
/* Скопировать backtable (модификаторы) комнаты room в out[30]. Исходное
* состояние per-room modifier (openness ворот, состояние пик и т.п.) для
* ленивой инициализации room_modif в pop_trob. Вне диапазона → нули. */
void pop_level_room_bg(uint8_t room, uint8_t *out30);
/* Таблицы связей дверей (LINKLOC@1440 / LINKMAP@1696, по 256 Б). Копируются
* в W2-RAM при загрузке (LINKMAP мутабельна — таймеры кнопок). i — индекс
* цепочки (модификатор кнопки). Декод — в pop_trob (get_doorlink_*). */
uint8_t pop_doorlink1(uint8_t i);
uint8_t pop_doorlink2(uint8_t i);
void pop_doorlink2_set(uint8_t i, uint8_t v);
/* Ряд приземления падающего loose-куска в колонке col комнаты room: первый
* НЕ-пустой тайл сверху вниз; возвращает его ряд, если это floor (→ debris),
* иначе -1 (пролетел/крушение о стену). */
+275 -30
View File
@@ -20,6 +20,7 @@
#include "pop_map.h"
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
#define TILE_EMPTY 0
@@ -31,6 +32,9 @@
#define TILE_WALL 20
#define TILE_LOOSE 11 /* проваливающийся пол */
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
#define TILE_SPIKE 2 /* пики */
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
#define ACT_STAND 0
#define ACT_RUNJUMP 1
#define ACT_HANGCLIMB 2
@@ -38,6 +42,7 @@
#define ACT_FREEFALL 4
#define ACT_BUMPED 5
#define ACT_HANGSTRAIGHT 6
#define ACT_TURN 7
#define SEQ_7_FALL 7 /* stepfall (stand/run/step/crouch) */
#define SEQ_17_SOFT_LAND 17
@@ -58,6 +63,13 @@
#define SEQ_23_RELEASE_LEDGE_FALL 23 /* отпустить уступ и падать */
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
/* Кадры анимации для check_spiked (seg006:0658). */
#define FRAME_7_RUN 7
#define FRAME_26_STANDJUMP_LAND 26
#define FRAME_34_RUNJUMP_START 34
#define FRAME_43_RUNJUMP_LAND 43
#define FRAME_NEEDS_FLOOR 0x40
#define FRAME_WEIGHT_X 0x1F
@@ -86,7 +98,11 @@ static int8_t g_infrontx; /* колонка «перед персонаже
/* ---- Карта комнаты ------------------------------------------------- */
uint8_t pop_fell_out; /* 1 = Kid пересёк НИЖНЮЮ границу (см. pop_map.h) */
uint8_t pop_leave_dir; /* уход вбок/вверх: 0=нет; 1=left,2=right,3=up (side+1) */
uint8_t pop_leave_timer; /* exit_room_timer (seg002): >0 = leave заблокирован N кадров после входа в комнату */
uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавшего loose (кусок улетел вниз) */
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
static uint8_t g_room; /* номер ТЕКУЩЕЙ комнаты (для room_modif пик/ворот) */
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) */
@@ -100,6 +116,18 @@ static uint8_t g_lcol[3], g_rcol[3];
void pop_map_set(uint8_t *fg) { g_fg = fg; }
void pop_map_set_room(uint8_t room) { g_room = room; }
void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; }
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
static uint8_t take_hp(uint8_t count)
{
if (count >= hitp_curr) { hitp_curr = 0; return 1; }
hitp_curr = (uint8_t)(hitp_curr - count);
return 0;
}
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
const uint8_t *lcol_fg, const uint8_t *rcol_fg)
{
@@ -123,6 +151,28 @@ static uint8_t get_tile(int8_t col, int8_t row)
return g_curr_tile;
}
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
* tile_is_floor это ПОЛ), поэтому Kid НЕ падает на них. Раньше get_tile ради
* проходимости подменял ворота шва на empty — но подмену видел и check_on_floor
* → start_fall (Kid проваливался при проходе через ОТКРЫТЫЕ ворота шва). Теперь
* проходимость живёт ТОЛЬКО в bump-логике (как в SDLPoP), не трогая проверку пола.
* Модификатор: шов слева (col=-1) — сосед g_link_l col9; шов справа (col=10) —
* g_link_r col0; ВНУТРИ комнаты (0..9) — текущая комната g_room (иначе после
* перехода Kid встаёт на ту же створку уже как на внутрикомнатную и его отбивает
* обратно). Открыто ⇔ (modif>>2)+6 >= char_height (бары выше головы). */
static uint8_t gate_passable(int8_t col, int8_t row)
{
uint8_t gm;
if (row < 0 || row > 2) return 0;
if (col == -1 && g_link_l) gm = pop_trob_modif(g_link_l)[row * 10 + 9];
else if (col == 10 && g_link_r) gm = pop_trob_modif(g_link_r)[row * 10 + 0];
else if (col >= 0 && col <= 9) gm = pop_trob_modif(g_room)[row * 10 + col];
else return 0;
return (uint8_t)((gm >> 2) + 6) >= (uint8_t)kid_fp_height();
}
static uint8_t tile_is_floor(uint8_t t)
{
switch (t) {
@@ -225,13 +275,31 @@ static int distance_to_edge(int xpos)
static int distance_to_edge_weight(void) { return distance_to_edge(dx_weight()); }
/* ---- ТОЧНАЯ коллизия со стеной (seg004) --------------------------- *
* Передний край персонажа char_x_*_coll = char_dx_forward(cur_frame.dx)
* (obj_x/2+58 сокращается ровно к этому; ширина спрайта НЕ нужна), ∓4 для
* THIN-кадров. dist_from_wall_forward — расстояние переднего края до грани
* стены tiletype в колонке tcol: >0 зазор, <=0 достал/зашёл (насколько). */
* Передний край персонажа = char_x_right (взгляд вправо) / char_x_left
* (влево) из set_char_collision (seg006:0723): obj_x/2 + 58, где obj_x =
* 2*char_dx_forward(cur_frame.dx) - 116 и load_frame_to_obj (seg008:1728)
* добавляет +1 для кадров «чётного пикселя» (бит 0x80 флагов XOR знак
* направления). Деление obj_x/2 в C усечено К НУЛЮ, поэтому в зоне левого
* шва (e<=57, obj_x<0) поправка +1 сдвигает край на пиксель ВПРАВО: e -> e+1.
* Это НЕ мелочь: Kid, осевший после отскока от ворот шва на x=57, имеет
* char_x_right=58 и НЕ пересекает порог leave-left (<=57) — иначе пинг-понг
* drawn_room у закрытых ворот (BUG-SEAM-PINGPONG; подтверждено потиковой
* трассой живого SDLPoP 1.23). При e>=58 формула сокращается к чистому
* char_dx_forward. ∓4 для THIN-кадров поверх. dist_from_wall_forward —
* расстояние переднего края до грани стены tiletype в колонке tcol: >0
* зазор, <=0 достал/зашёл (насколько). */
static int char_x_forward_edge(void)
{
int e = char_dx_forward(kid_cur_dx());
uint8_t fl = kid_cur_flags();
if (Kid.direction < 0) fl ^= 0x80;
if (!(fl & 0x80) && e <= 57) e++; /* odd pixel + C-трунк к нулю */
return e;
}
static int char_front_coll(void)
{
int cx = char_dx_forward(kid_cur_dx());
int cx = char_x_forward_edge();
if (kid_cur_flags() & FRAME_THIN)
cx += (Kid.direction < 0) ? 4 : -4; /* THIN: край внутрь */
return cx;
@@ -270,10 +338,36 @@ static void in_wall(void)
/* ---- Падение ------------------------------------------------------- */
static void inc_curr_row(void) { Kid.curr_row++; }
/* Пики (определены ниже, перед pop_phys_tick) — нужны в land() для смерти
* при ПАДЕНИИ на выдвинутые пики (seg005 loc_5EE6). */
static int is_spike_harmful(uint8_t tilepos);
static void spiked(uint8_t tilepos);
/* fell_on_spikes (seg005 loc_5EE6): при приземлении тайл ПОД персонажем =
* пика (или пика СЗАДИ у самой кромки) и вредна → напороться (смерть).
* Возвращает 1, если умер (land прерывается). */
static uint8_t fell_on_spikes(void)
{
uint8_t tp;
if (get_tile_at_char() == TILE_SPIKE) {
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
} else if (distance_to_edge_weight() >= 12 &&
get_tile_behind_char() == TILE_SPIKE) {
int8_t bcol = (int8_t)(dir_behind[Kid.direction + 1] + Kid.curr_col);
if (bcol < 0 || bcol > 9) return 0;
tp = (uint8_t)(Kid.curr_row * 10 + bcol);
} else {
return 0;
}
if (is_spike_harmful(tp)) { spiked(tp); return 1; }
return 0;
}
static void land(void)
{
uint8_t seq;
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
if (fell_on_spikes()) return; /* упал на вредные пики — смерть */
/* к краю пола — чуть назад (как оригинал) */
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
Kid.x = (uint8_t)char_dx_forward(-3);
@@ -618,7 +712,7 @@ static void check_action(void)
* выравниваем, без рестарта. В падении/висе не трогаем. */
static void check_bumped(void)
{
uint8_t t; int8_t tcol; int d;
uint8_t t, wt; int8_t tcol; int d;
/* Не бампим в падении/висе/подтягивании (seg004 check_bumped guard'ы:
* action hang_climb/hang_straight + кадры подъёма 135..148 — иначе
* climb-up у стены прерывается отскоком). FREEFALL — наш guard: стену
@@ -635,8 +729,19 @@ static void check_bumped(void)
* underflow X и уход в стену. fell_out ловит do_fall. */
if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
t = get_tile_at_char(); tcol = Kid.curr_col;
if (!wall_type(t)) { t = get_tile_infrontof_char(); tcol = g_infrontx; }
if (!wall_type(t)) return;
wt = wall_type(t);
/* seg004: bump проверяется по колонке СБОКУ от стены (bump_col_left/right_
* of_wall), а НЕ по тайлу ПОД персонажем. Стоя́ на односторонней стене
* (ворота = стена справа / зеркало = стена слева) лицом ПРОЧЬ от её
* блокирующей стороны — она не бампит. Иначе разворот на тайле ворот у
* шва выталкивает Kid к дальней грани ворот (сквозь закрытую решётку в
* соседнюю комнату — баг «телепорта» при развороте). */
if ((wt == 1 && Kid.direction < 0) || (wt == 2 && Kid.direction >= 0))
wt = 0;
if (!wt) { t = get_tile_infrontof_char(); tcol = g_infrontx; wt = wall_type(t); }
if (!wt) return;
if (t == TILE_GATE && gate_passable(tcol, Kid.curr_row))
return; /* открытые ворота (шов/внутри) — Kid проходит (не бамп) */
d = dist_from_wall_forward(t, tcol);
if (d >= 0) return; /* ещё не зашёл за грань */
Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */
@@ -676,11 +781,20 @@ static void check_press(void)
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;
/* стоя́ние: action turn/bumped или < hang_climb (0/1), с FRAME_NEEDS_FLOOR.
* ACT_TURN обязателен (SDLPoP check_press: actions_7_turn): при развороте
* на кнопке иначе trigger_button не зовётся кадр-два → таймер связи падает
* <2 → кнопка ложно отжимается и жмётся снова. */
if (action != ACT_BUMPED && action != ACT_TURN && 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);
{
uint8_t t = get_tile_at_char();
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
if (t == TILE_OPENER || t == TILE_CLOSER)
pop_trigger_button(g_room, tp, t, pop_trob_modif(g_room)[tp]);
else if (t == TILE_LOOSE)
make_loose_fall(tp, 1);
}
}
/* do_knock (seg007:0FE0) + loose_make_shake (seg007:0FB4): сотрясение от
@@ -729,29 +843,32 @@ void pop_loose_reset(void)
void pop_loose_tick(void)
{
int pos;
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
* нет аппаратного деления, и SDCC выносил оба деления (__divuchar/
* __moduchar) в начало КАЖДОЙ итерации (общее подвыражение аргументов
* вызовов), т.е. 30*2=60 вызовов деления за кадр — даже в покое и в
* комнатах без loose-плит (условия if к тому моменту ещё не проверены).
* Инкремент+сравнение считают row/col без деления. */
uint8_t row = 0, col = 0;
for (pos = 0; pos < 30; pos++) {
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */
pop_loose_bake_empty(pos / 10, pos % 10);
pop_loose_bake_empty(row, col);
loose_bake[pos]--;
continue;
}
if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
* страницы дабл-буфера — иначе на одной остаётся застывший
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
pop_loose_shake_draw(pos / 10, pos % 10);
pop_loose_shake_draw(row, col);
loose_rest[pos]--;
continue;
}
if (pop_loose_modif[pos] == 0) continue;
{
} else if (pop_loose_modif[pos] != 0) {
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 {
pop_loose_shake_draw(row, col); /* дрожащий кадр */
}
} else if (m >= 11) { /* loose_floor_delay — падение */
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
@@ -761,14 +878,15 @@ void pop_loose_tick(void)
* (стена-вниз от тайла снизу + чёрный фон). */
g_fg[pos] = TILE_EMPTY;
pop_loose_modif[pos] = 0;
pop_loose_bake_empty(pos / 10, pos % 10); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */
loose_bake[pos] = 1; /* + вторая страница следующим кадром */
pop_loose_mob_spawn(pos / 10, pos % 10); /* отрыв падающего куска (L4) */
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
continue;
} else { /* отсчёт 1..10 — дрожащий кадр */
pop_loose_shake_draw(row, col);
}
pop_loose_shake_draw(pos / 10, pos % 10); /* дрожащий кадр */
}
if (++col == 10) { col = 0; row++; } /* row/col без деления */
}
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
}
@@ -799,9 +917,27 @@ static void check_leave_below(void)
* удержит у шва). */
static void check_leave(void)
{
int fx = char_dx_forward(kid_cur_dx());
int fx;
uint8_t f = Kid.frame;
/* seg002 leave_room: комнату НЕ покидаем во время разворота (action turn),
* подъёма-с-зацепа (climb, кадры 135..149) и вставания из приседа
* (110..119). Без этого гарда разворот у правого шва (взгляд флипается
* право→лево, char_x_left проскакивает >=198) телепортирует Kid сквозь
* закрытые ворота в соседнюю комнату (leave_room возвращает 1). */
if (Kid.action == ACT_TURN ||
(f >= 135 && f < 150) ||
(f >= 110 && f < 120))
return;
/* Передний край — ТОЛЬКО через char_x_forward_edge (obj_x-арифметика
* set_char_collision): в зоне шва (<=57) он на пиксель правее чистого
* Kid.x+dx, и Kid, осевший у ворот на x=57, порога leave-left НЕ достигает
* (эталон SDLPoP: экран не переключается, BUG-SEAM-PINGPONG). */
fx = char_x_forward_edge();
if (Kid.direction >= 0) { /* взгляд вправо: fx = char_x_right */
if (g_link_r && fx >= 201) {
uint8_t t9 = get_tile(9, Kid.curr_row);
if (g_link_r && fx >= 201 &&
t9 != TILE_DOORTOP_FLOOR && t9 != TILE_DOORTOP) {
/* leave_room (seg002): под doortop у правой кромки выхода нет */
Kid.x = (uint8_t)(Kid.x - 140); determine_col(); pop_leave_dir = 2;
} else if (g_link_l && fx <= 57) {
Kid.x = (uint8_t)(Kid.x + 140); determine_col(); pop_leave_dir = 1;
@@ -815,8 +951,105 @@ static void check_leave(void)
}
}
/* ---- Пики (spike, seg006/seg007) ---------------------------------- *
* Состояние пики в room_modif[g_room][tilepos] (pop_trob), автомат —
* animate_spike (pop_trob). Здесь: триггер выдвижения (check_spike_below),
* решение о смерти (check_spiked) + spiked(). */
/* start_anim_spike (seg007:08E3): взвести выдвижение, если пика в покое
* (modif 0 → trob type1) или ушла не полностью (modif<0, кроме 0xFF disabled
* → мгновенно 0x8F «вышли»). */
static void start_anim_spike(uint8_t tilepos)
{
uint8_t *mod = pop_trob_modif(g_room);
int8_t old;
if (!mod) return;
old = (int8_t)mod[tilepos];
if (old <= 0) {
if (old == 0)
pop_add_trob(g_room, tilepos, 1); /* (TODO: sound_49_spikes) */
else if ((uint8_t)old != 0xFF)
mod[tilepos] = 0x8F;
}
}
/* is_spike_harmful (seg007:1178): 0/-1 → 0 (безопасно); <0 → 1; 1..4 → 2;
* >=5 → 0. */
static int is_spike_harmful(uint8_t tilepos)
{
int8_t m = (int8_t)pop_trob_modif(g_room)[tilepos];
if (m == 0 || m == -1) return 0;
if (m < 0) return 1;
if (m < 5) return 2;
return 0;
}
/* spiked (seg005:0888): напоролся — пика становится безвредной (0xFF),
* выровнять по колонке, take_hp(100) = смерть, seq_51 (импейл). */
static void spiked(uint8_t tilepos)
{
pop_trob_modif(g_room)[tilepos] = 0xFF;
Kid.y = (uint8_t)y_land[Kid.curr_row + 1];
Kid.x = (uint8_t)(x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
Kid.x = (uint8_t)char_dx_forward(8);
Kid.fall_y = 0;
take_hp(100); /* (TODO: sound_48_spiked) */
kid_set_seq(SEQ_51_SPIKED);
play_seq();
determine_col();
pop_kid_dead = 1;
}
/* check_spiked (seg006:0658): под персонажем пика И вредна И кадр
* бега(7..14)/старта прыжка(34..39) при harmful>=2, ЛИБО кадр приземления
* (43/26) при harmful!=0 → smert'. */
static void check_spiked(void)
{
uint8_t frame = Kid.frame, tp;
int h;
if (Kid.curr_col < 0 || Kid.curr_col > 9 || Kid.curr_row < 0 || Kid.curr_row > 2)
return;
if (get_tile_at_char() != TILE_SPIKE) return;
tp = (uint8_t)(Kid.curr_row * 10 + Kid.curr_col);
h = is_spike_harmful(tp);
if ((h >= 2 && ((frame >= FRAME_7_RUN && frame < 15) ||
(frame >= FRAME_34_RUNJUMP_START && frame < 40))) ||
((frame == FRAME_43_RUNJUMP_LAND || frame == FRAME_26_STANDJUMP_LAND) && h != 0))
spiked(tp);
}
/* check_spike_below (seg006:1199): для каждой колонки РЕАЛЬНОГО габарита
* спрайта (char_x_left..char_x_right, порт set_char_collision) скан ВНИЗ от
* curr_row через НЕ-floor тайлы; встретил пику → start_anim_spike. Габарит,
* а не «curr_col + колонка спереди»: иначе пики выдвигаются на тайл раньше
* (когда тело Кида ещё не над шахтой). Метрики кадра — kid_fp (габарит
* последнего kid_draw; отставание в 1 кадр несущественно, ошибка в позднюю
* сторону). */
static void check_spike_below(void)
{
int wh = ((int)kid_fp_width() + 1) >> 1; /* char_width_half */
int xl = kid_fp_obj_x() / 2 + 58; /* char_x_left */
int xr;
int8_t c0, c1, col;
if (Kid.direction >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh; /* char_x_right */
c0 = get_tile_div_mod_m7(xl);
c1 = get_tile_div_mod_m7(xr);
for (col = c0; col <= c1; col++) {
int8_t row = Kid.curr_row;
if (col < 0 || col > 9) continue;
for (;;) {
uint8_t t = get_tile(col, row);
if (t == TILE_SPIKE) { start_anim_spike((uint8_t)(row * 10 + col)); break; }
if (!tile_is_floor(t) && row <= 2) { row++; continue; }
break;
}
}
}
void pop_phys_tick(void)
{
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
if (grab_timer) grab_timer--;
fall_accel();
fall_speed();
@@ -824,7 +1057,19 @@ void pop_phys_tick(void)
check_bumped(); /* удержать у стены до check_action (порядок PoP) */
check_action();
check_press(); /* seg006: стойка/пробой loose → make_loose_fall */
check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */
check_spiked(); /* seg006: напоролся на вредные пики → смерть */
if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */
pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
check_leave(); /* seg002: уход вбок (право) → переход в соседнюю комнату */
/* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой
* leave заблокирован. Без него транзиент отскока о ворота шва (seq_47
* роняет char_x за порог) сразу же выкидывает Kid в соседнюю комнату
* (ping-pong / баг #4). С таймером первый бамп попадает в окно блокировки,
* Kid оседает у грани (char_x стабильно > порога) — экран не перескакивает. */
if (pop_leave_timer) {
pop_leave_timer--;
} else {
check_leave_below(); /* seg002: y>=211 → переход в комнату снизу */
check_leave(); /* seg002: уход вбок → переход в соседнюю комнату */
}
}
+17
View File
@@ -25,11 +25,28 @@ void pop_map_set(uint8_t *fg);
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
const uint8_t *lcol_fg, const uint8_t *rcol_fg);
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
* Звать в enter_room. */
void pop_map_set_room(uint8_t room);
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
* респавн). */
extern uint8_t pop_kid_dead;
extern uint8_t hitp_curr;
void pop_kid_hp_reset(void);
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
extern uint8_t pop_leave_dir;
/* exit_room_timer (порт seg002 exit_room): пока >0, боковой/нижний leave
* заблокирован (декремент в pop_phys_tick). Приложение ставит =2 при входе
* в комнату (enter_room) — 2 кадра защиты от повторного/транзиентного leave
* (ping-pong у шва, транзиент отскока о ворота, баг #4/BUG-2). */
extern uint8_t pop_leave_timer;
/* Loose floors (проваливающиеся полы). pop_loose_modif[30] — состояние на
* тайл (0 покой / 0x80.. тряска / 1..11 отсчёт падения); читает pop_bg при
* отрисовке. pop_loose_tick() — анимация + перерисовка изменившихся тайлов
+324
View File
@@ -0,0 +1,324 @@
/*
* pop_trob.c — P0-каркас: room_modif + список trob + диспетчер анимаций.
* Порт SDLPoP seg007 (process_trobs/animate_tile/add_trob/animate_spike).
* См. pop_trob.h.
*/
#include "pop_trob.h"
#include "pop_level.h"
#include "pop_bg.h" /* pop_spike_redraw */
/* Коды тайлов (fg & 0x1F), которые анимируются. */
#define TILE_SPIKE 0x02
#define TILE_GATE 0x04
#define TILE_CLOSER 0x06
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
#define TILE_OPENER 0x0F
/* ---- Персистентное per-room modifier-состояние -------------------- */
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
static uint8_t room_seen[POP_NROOMS]; /* modif проинициализирован из уровня */
/* ---- Список анимируемых тайлов ------------------------------------ */
typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
#define TROBS_MAX 24
static trob_t trobs[TROBS_MAX];
static uint8_t trobs_count;
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
/* Отложенная перерисовка «покоя» законченных пик/кнопок текущей комнаты
* (2 кадра = обе страницы дабл-буфера, как loose_rest). */
static uint8_t spike_rest[POP_ROOMTILES];
static uint8_t button_rest[POP_ROOMTILES];
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
* стоящим Кидом перерисовывалась КАЖДЫЙ кадр (2×draw_tile ≈ 50% кадра в
* статике), т.к. её trob висит, пока Кид стоит (это верно, как в SDLPoP —
* расхождение было именно в безусловном редрое). */
static uint8_t trob_drawn[POP_ROOMTILES];
void pop_trob_reset(void)
{
uint8_t i;
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
trobs_count = 0;
}
uint8_t *pop_trob_modif(uint8_t room)
{
if (room < 1 || room > POP_NROOMS) return 0;
if (!room_seen[room - 1]) {
pop_level_room_bg(room, room_modif[room - 1]);
room_seen[room - 1] = 1;
}
return room_modif[room - 1];
}
static int find_trob(uint8_t room, uint8_t tilepos)
{
uint8_t i;
for (i = 0; i < trobs_count; i++)
if (trobs[i].room == room && trobs[i].tilepos == tilepos) return i;
return -1;
}
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
{
int f = find_trob(room, tilepos);
if (f >= 0) { trobs[f].type = type; return; }
if (trobs_count >= TROBS_MAX) return;
trobs[trobs_count].room = room;
trobs[trobs_count].tilepos = tilepos;
trobs[trobs_count].type = type;
trobs_count++;
}
void pop_trob_room_changed(void)
{
uint8_t i;
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
}
/* animate_spike (seg007:04D3): автомат по modifier. *m — modifier тайла,
* *type — тип trob (>=0 активен, <0 = удалить). Выдвиг: ++modif (1..4),
* на 5 → 0x8F (полностью вышли, безвредны), на 9 → 0 + конец. Убирание
* (флаг 0x80): --modif; когда 0x7F-часть обнулилась → modif=6 (начать
* визуальный уход 6..8). 0xFF = disabled. */
static void animate_spike(uint8_t *m, int8_t *type)
{
uint8_t mod = *m;
if (*type >= 0) {
if (mod == 0xFF) return; /* disabled — не двигать */
if (mod & 0x80) {
--mod;
if (mod & 0x7F) { *m = mod; return; }
mod = 6;
} else {
++mod;
if (mod == 5) mod = 0x8F;
else if (mod == 9) { mod = 0; *type = -1; }
}
}
*m = mod;
}
/* ---- Кнопки/ворота (seg007) --------------------------------------- *
* Таблицы связей LINKLOC/LINKMAP — в pop_level (мутабельная копия в W2).
* doorlink1 (d1) = LINKLOC, doorlink2 (d2) = LINKMAP. */
static uint8_t get_doorlink_timer(uint8_t i) { return (uint8_t)(pop_doorlink2(i) & 0x1F); }
static void set_doorlink_timer(uint8_t i, uint8_t v)
{
pop_doorlink2_set(i, (uint8_t)((pop_doorlink2(i) & 0xE0) | (v & 0x1F)));
}
static uint8_t get_doorlink_tile(uint8_t i) { return (uint8_t)(pop_doorlink1(i) & 0x1F); }
static uint8_t get_doorlink_next(uint8_t i) { return (uint8_t)(!(pop_doorlink1(i) & 0x80)); }
static uint8_t get_doorlink_room(uint8_t i)
{
return (uint8_t)(((pop_doorlink1(i) & 0x60) >> 5) + ((pop_doorlink2(i) & 0xE0) >> 3));
}
/* trigger_gate (seg007:092C): изменить openness ворот по типу кнопки.
* Возвращает тип анимации для add_trob (или -1 = ничего). */
static int8_t trigger_gate(uint8_t room, uint8_t tilepos, uint8_t button_type)
{
uint8_t *mod = pop_trob_modif(room);
uint8_t modifier = mod[tilepos];
if (button_type == TILE_OPENER) {
if (modifier == 0xFF) return -1; /* уже насовсем открыт */
if (modifier >= 188) { mod[tilepos] = 238; return -1; } /* открыт → держать */
mod[tilepos] = (uint8_t)((modifier + 3) & 0xFC);
return 1; /* обычное открытие */
} else if (button_type == TILE_DEBRIS) { /* насовсем (died_on_button) */
if (modifier < 188) return 2;
mod[tilepos] = 0xFF;
return -1;
} else { /* closer */
if (modifier != 0) return 3; /* быстро закрыть */
return -1;
}
}
/* trigger_1 (seg007:0999): цель = ворота → trigger_gate; иначе -1 (leveldoor
* и прочее — отложено). */
static int8_t trigger_1(uint8_t target_type, uint8_t room, uint8_t tilepos, uint8_t button_type)
{
if (target_type == TILE_GATE)
return trigger_gate(room, tilepos, button_type);
return -1;
}
/* do_trigger_list (seg007:09E5): обойти цепочку LINKLOC от index, для каждой
* цели добавить trob (если trigger_1 >= 0). */
static void do_trigger_list(uint8_t index, uint8_t button_type)
{
for (;;) {
uint8_t room = get_doorlink_room(index);
uint8_t tilepos = get_doorlink_tile(index);
uint8_t target_type = pop_level_tile(room, tilepos);
int8_t result = trigger_1(target_type, room, tilepos, button_type);
if (result >= 0) pop_add_trob(room, tilepos, result);
if (get_doorlink_next(index) == 0) break;
index++;
}
}
void pop_trigger_button(uint8_t room, uint8_t tilepos,
uint8_t button_type, uint8_t modifier)
{
uint8_t link_timer = get_doorlink_timer(modifier);
if (link_timer != 0x1F) { /* не заклинено */
set_doorlink_timer(modifier, 5);
if (link_timer < 2)
pop_add_trob(room, tilepos, 1); /* кнопка нажимается (+ TODO sound) */
do_trigger_list(modifier, button_type);
}
}
/* animate_button (seg007:0D3A): отсчитать таймер связи; <2 → кнопка
* отжимается (trob кончился). idx = модификатор кнопки (индекс LINKLOC). */
static void animate_button(uint8_t idx, int8_t *type)
{
if (*type >= 0) {
uint8_t timer = get_doorlink_timer(idx);
timer = (uint8_t)(timer ? timer - 1 : 0);
set_doorlink_timer(idx, timer);
if (timer < 2) *type = -1;
}
}
/* animate_door (seg007:0522): openness по типу анимации. *m — modif ворот,
* *type — 0 закрытие / 1 открытие / 2 насовсем / 3..8 быстрое закрытие. */
static const uint8_t gate_close_speeds[9] = {0, 0, 0, 20, 40, 60, 80, 100, 120};
static const int8_t door_delta[3] = {-1, 4, 4};
static void animate_door(uint8_t *m, int8_t *type)
{
int8_t anim_type = *type;
uint8_t mod = *m;
if (anim_type >= 0) {
if (anim_type >= 3) { /* быстрое закрытие */
if (anim_type < 8) { anim_type++; *type = anim_type; }
{
int new_mod = (int)mod - gate_close_speeds[anim_type];
if (new_mod < 0) { mod = 0; *type = -1; }
else mod = (uint8_t)new_mod;
}
} else if (mod != 0xFF) {
mod = (uint8_t)(mod + door_delta[anim_type]);
if (anim_type == 0) { /* закрытие */
if (mod == 0) *type = -1; /* gate_stop */
} else { /* открытие */
if (mod >= 188) {
if (anim_type < 2) { mod = 238; *type = 0; } /* открыт → потом закрывать */
else { mod = 0xFF; *type = -1; } /* насовсем */
}
}
} else {
*type = -1;
}
}
*m = mod;
}
uint8_t pop_trob_room_active(uint8_t room)
{
uint8_t i;
for (i = 0; i < trobs_count; i++)
if (trobs[i].room == room) return 1;
return 0;
}
void pop_process_trobs(uint8_t cur_room)
{
uint8_t i, w = 0;
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
{
uint8_t row = 0, col = 0;
for (i = 0; i < POP_ROOMTILES; i++) {
if (spike_rest[i]) {
pop_spike_redraw(row, col);
spike_rest[i]--;
}
if (button_rest[i]) {
pop_button_redraw(row, col);
button_rest[i]--;
}
if (++col == 10) { col = 0; row++; }
}
}
if (trobs_count == 0) return;
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
* pop_level_tile() на каждый trob (тот свапал W0 каждый вызов = N свапов
* за кадр даже без анимации). Дальше цикл (с редроями) идёт с
* размапленным W0 — как раньше. */
{
uint8_t k;
pop_level_access_begin();
for (k = 0; k < trobs_count; k++)
trob_code[k] = pop_level_tile_raw(trobs[k].room, trobs[k].tilepos);
pop_level_access_end();
}
for (i = 0; i < trobs_count; i++) {
uint8_t room = trobs[i].room;
uint8_t tp = trobs[i].tilepos;
uint8_t code = trob_code[i];
uint8_t *mod = pop_trob_modif(room);
int8_t type = trobs[i].type;
switch (code) {
case TILE_SPIKE:
animate_spike(&mod[tp], &type);
break;
case TILE_OPENER:
case TILE_CLOSER:
animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */
break;
case TILE_GATE:
animate_door(&mod[tp], &type);
break;
default:
type = -1;
break;
}
trobs[i].type = type;
if (room == cur_room &&
(code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) {
/* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из
* SDLPoP). spike: draw-сигнатура = modifier; button: = «нажата»
* (таймер связи >1). Пока Кид держит кнопку — сигнатура стабильна
* → редроя нет. При изменении рисуем ТЕКУЩУЮ страницу сразу +
* <rest>=1 (вторая страница дабл-буфера следующим кадром через
* rest-цикл). tp/10,tp%10 считаем только в момент редроя (редко),
* а не каждый кадр. */
/* Сигнатура = ВИДИМЫЙ кадр, а не сырой modifier. Для пики кадр =
* (mod&0x80)?5:mod (как в draw_tile): весь ретракт-hold 0x80..0x8F
* (16 значений modifier) рисуется одним кадром 5 → сырой modifier
* дал бы 16 ложных редроёв «выдвинутой» неподвижной пики. */
uint8_t sig = (code == TILE_SPIKE)
? (uint8_t)((mod[tp] & 0x80) ? 5 : mod[tp])
: (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
if (sig != trob_drawn[tp]) {
uint8_t trow = tp / 10, tcol = tp % 10;
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; }
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; }
trob_drawn[tp] = sig;
}
}
}
/* compact: удалить завершённые (type < 0). */
for (i = 0; i < trobs_count; i++)
if (trobs[i].type >= 0) trobs[w++] = trobs[i];
trobs_count = w;
}
+61
View File
@@ -0,0 +1,61 @@
/*
* pop_trob.h — P0-каркас: персистентное per-room modifier-состояние +
* список анимируемых тайлов (trob). Порт SDLPoP seg007 (process_trobs/
* add_trob/animate_tile) + curr_room_modif.
*
* ЗАЧЕМ: тайлы-объекты (пики, ворота, кнопки) имеют состояние (modifier),
* которое живёт МЕЖДУ входами в комнату и может меняться КРОСС-КОМНАТНО
* (кнопка в одной комнате открывает ворота в другой). room_modif[room]
* — единственный источник модификаторов для отрисовки (pop_bg draw_tile
* читает его как bg), как curr_room_modif в оригинале.
*
* Список trob хранит (room, tilepos, type) активных анимаций; каждый кадр
* pop_process_trobs продвигает их (animate_spike/door/button по коду тайла),
* пишет результат в room_modif и перерисовывает изменившиеся тайлы ТЕКУЩЕЙ
* комнаты на back-странице (2-page, как loose). Невидимые комнаты
* анимируются в состоянии (без отрисовки) — при входе рисуются статически.
*/
#ifndef POP_TROB_H
#define POP_TROB_H
#include <stdint.h>
#define POP_NROOMS 24
#define POP_ROOMTILES 30
/* Сброс всего состояния (новый уровень / рестарт): забыть все комнаты и
* очистить список trob. */
void pop_trob_reset(void);
/* Указатель на persistent modifier-массив комнаты room (1..24), 30 байт.
* При ПЕРВОМ обращении лениво инициализируется из backtable уровня
* (pop_level_room_bg). Далее ЖИВЁТ (не перезагружается). room вне
* диапазона → 0. */
uint8_t *pop_trob_modif(uint8_t room);
/* Добавить/обновить trob (room, tilepos, type). Если уже есть — сменить
* type (как SDLPoP add_trob/find_trob). type<0 = будет удалён. */
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type);
/* Продвинуть все trob на кадр (animate_* по коду тайла из уровня), записать
* modif, перерисовать тайлы ТЕКУЩЕЙ (cur_room) комнаты на back-странице.
* Звать в основном цикле ПОСЛЕ set_draw_page(back), рядом с loose. */
void pop_process_trobs(uint8_t cur_room);
/* Кид встал на кнопку (opener/closer): взвести таймер связи + обойти цепочку
* LINKLOC, добавив trob целям (ворота). Порт seg007 trigger_button.
* room/tilepos — кнопка; button_type — её тайл-код; modifier — её modif
* (= индекс в LINKLOC). Звать из check_press (pop_map). */
void pop_trigger_button(uint8_t room, uint8_t tilepos,
uint8_t button_type, uint8_t modifier);
/* Есть ли активные trob в комнате room? Для перерисовки ворот соседа через
* шов (roomtest не дёргает шов, пока сосед не анимируется). */
uint8_t pop_trob_room_active(uint8_t room);
/* Смена комнаты: сбросить отложенные перерисовки «покоя» текущей комнаты
* (trob-состояние в room_modif персистентно и НЕ трогается). Звать в
* enter_room. */
void pop_trob_room_changed(void);
#endif
+153 -7
View File
@@ -14,18 +14,58 @@
#include "pop_ctrl.h"
#include "pop_map.h"
#include "pop_level.h"
#include "pop_trob.h"
__sfr __at (0xFE) io_border;
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
#define SEQ_STAND 2
#define ROOM_1 1
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
/* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4
* row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту →
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
#define START_ROOM 6
#define START_COL 1
#define START_ROW 0
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
* чтобы enter_room переключал комнату без длинного списка параметров. */
static uint8_t room_fg[30], room_bg[30];
static uint8_t lcol_fg[3], lcol_bg[3], rcol_fg[3], rcol_bg[3], below_fg[10];
static uint8_t cur_room; /* номер текущей комнаты (для переходов) */
static uint8_t above_fg[10], above_mod[10]; /* ряд 2 верхнего соседа (полоса кладки у потолка) */
static uint8_t cur_room; /* номер ОТРИСОВАННОЙ комнаты (drawn_room) */
static uint8_t kid_room; /* РЕАЛЬНАЯ комната Kid (S1: == cur_room; расходятся в S3 straddle) */
/* Рендер-смещение Kid по логической X (порт xpos_in_drawn_room): комната Kid
* слева от отрисованной → −140 (спрайт за левой кромкой), справа → +140, та
* же → 0. Звать каждый кадр перед kid_draw. */
static void update_kid_render_dx(void)
{
int dx = 0;
if (kid_room != cur_room) {
if (kid_room == pop_room_link(cur_room, 0)) dx = -140; /* left neighbour */
else if (kid_room == pop_room_link(cur_room, 1)) dx = 140; /* right neighbour */
}
pop_kid_set_render_dx(dx);
}
/* Change-driven редрой ЛЕВОГО шва (ворота соседа room8 видны в col0): тяжёлый
* draw_tile(0,0) в pop_room_redraw_seam_left зовём ТОЛЬКО когда openness гейта
* реально изменился. Пока Кид держит кнопку и гейт открыт — modifier стабилен
* → 0 редроёв (раньше шов рисовался безусловно КАЖДЫЙ кадр, пока сосед активен
* — тот же класс, что был у кнопки). seam_redraw=2 = обе страницы дабл-буфера. */
static uint8_t seam_sig; /* последняя нарисованная openness гейта в шве */
static uint8_t seam_redraw; /* оставшиеся кадры редроя (обе страницы) */
/* Отладочный лейбл номера комнаты — рисуем только при смене (outtextxy тяжёлая):
* sig = последняя показанная комната, redraw = остаток кадров (2 = обе страницы). */
static uint8_t room_label_sig = 0xFF;
static uint8_t room_label_redraw;
/* Персистентные debris-переопределения (упавший loose-кусок приземлился в
* комнате снизу → floor стал debris). Минимальный per-room override: живёт
@@ -51,6 +91,7 @@ static void add_debris_override(uint8_t room, uint8_t pos)
static void enter_room(uint8_t room)
{
uint8_t pg, i;
uint8_t *room_mod;
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
for (i = 0; i < dbr_cnt; i++) /* применить debris-переопределения */
if (dbr_room[i] == room) room_fg[dbr_pos[i]] = TILE_DEBRIS;
@@ -59,14 +100,49 @@ static void enter_room(uint8_t room)
pop_room_link(room, 2), pop_room_link(room, 3),
lcol_fg, rcol_fg);
pop_room_set_below(below_fg);
/* Ряд 2 верхнего соседа → полоса кладки у потолка (row0). fg — статика
* уровня, mod — живой room_modif (ленивая инициализация). */
{
uint8_t up = pop_room_link(room, 2), c;
if (up) {
uint8_t *um = pop_trob_modif(up);
pop_level_access_begin();
for (c = 0; c < 10; c++) above_fg[c] = pop_level_tile_raw(up, (uint8_t)(20 + c));
pop_level_access_end();
for (c = 0; c < 10; c++) above_mod[c] = um[20 + c];
pop_room_set_above(above_fg, above_mod);
} else {
pop_room_set_above(0, 0); /* нет комнаты сверху → пол (кромка) */
}
}
pop_map_set_room(room);
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
* а не по свежему backtable. Фаза P0. */
room_mod = pop_trob_modif(room);
/* Модификаторы швов — из ЖИВОГО room_modif соседей (openness ворот,
* видимых через шов, персистентен: ворота room8(0,9) видны в col0 room6).
* pop_room_load заполнил lcol_bg/rcol_bg статикой уровня — перекрываем. */
{
uint8_t lft = pop_room_link(room, 0), rgt = pop_room_link(room, 1);
if (lft) { uint8_t *m = pop_trob_modif(lft);
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
seam_sig = m[9]; } /* шов рисуется в обе страницы ниже — синхр. сигнатуру */
if (rgt) { uint8_t *m = pop_trob_modif(rgt);
rcol_bg[0] = m[0]; rcol_bg[1] = m[10]; rcol_bg[2] = m[20]; }
}
seam_redraw = 0; /* шов свежий на обеих страницах — редрой не нужен */
for (pg = 0; pg < 2; pg++) {
gfx_set_draw_page(pg);
setfillstyle(SOLID_FILL, BLACK);
bar(0, 0, 319, 255);
pop_room_draw(room, room_fg, room_bg, lcol_fg, lcol_bg);
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
}
cur_room = room;
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
pop_leave_timer = 2; /* exit_room_timer: 2 кадра блокировки leave (анти-ping-pong/#4) */
}
int main(void)
@@ -75,6 +151,7 @@ int main(void)
* по умолчанию (без мерцания); SPACE переключает в однобуфер для
* отладки багов рисования (edge по sp_prev, чтоб не дёргать). */
uint8_t front = 0, back, dbuf = 1, sp_prev = 0, frozen = 0;
uint16_t dead_frames = 0;
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
puts("pop_bg_load failed");
@@ -100,14 +177,17 @@ int main(void)
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
* ТОЛЬКО прямоугольник Кида (kid_heal), а не весь экран. */
/* Извлечь и отрисовать стартовую комнату 1 (в обе страницы). */
enter_room(ROOM_1);
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
pop_trob_reset();
enter_room(START_ROOM);
gfx_set_draw_page(0);
/* K2b: Kid стоит на уступе (row1), управляется с клавиатуры.
* ←/→ бег, ↓ присед, ↑+→ прыжок с места, Shift шаг (K3). ESC — выход.
* Карта коллизии (pop_map_set) уже задана в enter_room. */
kid_init(SEQ_STAND, 1, 1, 0);
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
kid_draw();
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
kid_fp_width(), kid_fp_height(),
@@ -122,6 +202,9 @@ int main(void)
while (!kbd_raw_down(KBD_ESC)) {
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
* переключ). Выключение возвращает показ на страницу 0. */
io_border = 2; /* синий - начало */
uint8_t sp = kbd_raw_down(KBD_SPACE);
if (sp && !sp_prev) {
dbuf ^= 1;
@@ -138,6 +221,7 @@ int main(void)
if (kbd_raw_down(0x16)) frozen = 1;
if (kbd_raw_down(0x1E)) frozen = 0;
if (frozen) {
io_border = 0; /* черный: ждём vsync */
gfx_wait_vsync();
continue;
}
@@ -157,9 +241,11 @@ int main(void)
enter_room(down); /* координаты Kid уже репроецированы pop_map */
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
} else { /* выпал из уровня (нет комнаты снизу) — респавн */
enter_room(ROOM_1);
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
enter_room(START_ROOM);
gfx_set_draw_page(back);
kid_init(SEQ_STAND, 1, 1, 0);
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
}
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
uint8_t nb = pop_room_link(cur_room, (uint8_t)(pop_leave_dir - 1));
@@ -169,18 +255,59 @@ int main(void)
gfx_set_draw_page(back);
}
}
io_border = 4; /* зеленый */
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */
io_border = 6; /* голубой */
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */
/* слой фона — ДО Kid) */
io_border = 2; /* синий */
{ /* ворота соседа слева (room8) видны в col0 — */
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
if (lft) {
uint8_t *m = pop_trob_modif(lft);
uint8_t g = m[9], prev = seam_sig;
seam_sig = g; /* СНАЧАЛА сохранить (обход SDCC z80 sub-store */
/* бага: `if(g!=s)s=g` эмитит store РАЗНОСТИ g-s, */
/* не g — sdcc_z80_cmp_store_a_bug) */
if (g != prev) seam_redraw = 2; /* openness изменился → обе страницы дабл-буфера */
if (seam_redraw) {
lcol_bg[0] = m[9]; lcol_bg[1] = m[19]; lcol_bg[2] = m[29];
pop_room_redraw_seam_left();
seam_redraw--;
}
}
}
io_border = 4; /* зеленый */
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
dead_frames++;
if (dead_frames > RESPAWN_SETTLE &&
(kbd_raw_down(KBD_UP) || dead_frames > RESPAWN_DELAY)) {
dead_frames = 0;
pop_trob_reset(); /* рестарт уровня: пики/ворота в исходное */
enter_room(START_ROOM);
gfx_set_draw_page(back);
kid_init(SEQ_STAND, START_COL, START_ROW, 0);
pop_kid_hp_reset();
}
}
io_border = 6; /* голубой */
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
uint8_t fcol = (uint8_t)((pop_loose_fell - 1) % 10);
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
pop_loose_fell = 0;
io_border = 2; /* синий */
if (down) {
int8_t lrow = pop_room_col_landing(down, fcol);
if (lrow >= 0) add_debris_override(down, (uint8_t)(lrow * 10 + fcol));
}
}
io_border = 4; /* зеленый */
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
io_border = 0; /* черный */
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
kid_fp_width(), kid_fp_height(),
Kid.direction, Kid.frame, Kid.action);
@@ -189,6 +316,25 @@ int main(void)
/* Пейсинг 3 кадра, затем tear-free флип на vblank: draw уже
* сделан (скрытая страница), wait_vsync ловит начало кадра,
* set_visible_page переключает во время бланка (gfx.h паттерн). */
io_border = 0; /* черный: ждём vsync */
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
* а не каждый кадр (обход sdcc_z80_cmp_store_a_bug: сначала сохранить). */
{
uint8_t r = cur_room;
if (r != room_label_sig) { room_label_sig = r; room_label_redraw = 2; }
if (room_label_redraw) {
char roomLabel[] = "Room ##";
roomLabel[5] = (char)(r / 10 + '0');
roomLabel[6] = (char)(r % 10 + '0');
setcolor(WHITE);
outtextxy(100, 0, roomLabel);
room_label_redraw--;
}
}
gfx_wait_vsync();
gfx_wait_vsync();
gfx_wait_vsync();
+18 -1
View File
@@ -56,6 +56,21 @@ CLIMB_OVERLAY_ENV_IDS = {32, 150, 151}
# попадают, поэтому добавляем явно. Без них дрожащая плита невидима.
LOOSE_ANIM_ENV_IDS = {69, 70, 71, 72, 73, 74}
# Кадры ВЫДВИЖЕНИЯ пик (seg008): три части — left (128-132, своя ячейка,
# draw_tile_anim), right (134-138, правый сосед, draw_tile_anim_right), fore
# (139-143, поверх Kid, draw_tile_fore). Рисуются ТОЛЬКО динамикой
# (animate_spike); в уровнях пики статически убраны (modif=0 → кадр 0),
# поэтому в used render_room НЕ попадают. Без них выдвинутые пики невидимы.
SPIKE_ANIM_ENV_IDS = {128, 129, 130, 131, 132, # left
134, 135, 136, 137, 138, # right
139, 140, 141, 142, 143} # fore
# Кадры ОТКРЫТИЯ ворот (portcullis, DOOR_FRAM_SLICE + gate pieces 50/51,
# seg008 draw_gate): 50-59 + 67. В уровнях ворота статически ЗАКРЫТЫ
# (низкий modif → используются только нижние срезы 50-53); срезы более
# открытых состояний (54-59, 67) нужны анимации открытия — добавляем явно.
GATE_ANIM_ENV_IDS = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 67}
ENV_SHIFT = 5 # env фон: страница = id>>5, idx = id&31
ENV_MASK = (1 << ENV_SHIFT) - 1
@@ -248,7 +263,9 @@ def main():
used = collect_used()
env_ids = sorted(set(id for ch, id in used if ch != R.CHTAB_WALL)
| CLIMB_OVERLAY_ENV_IDS # + пол-оверлей подъёма
| LOOSE_ANIM_ENV_IDS) # + дрожащие кадры loose
| LOOSE_ANIM_ENV_IDS # + дрожащие кадры loose
| SPIKE_ANIM_ENV_IDS # + кадры выдвижения пик
| GATE_ANIM_ENV_IDS) # + кадры открытия ворот
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]
+359
View File
@@ -0,0 +1,359 @@
Creative Commons Legal Code
Attribution-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined below) for the purposes of this
License.
c. "Creative Commons Compatible License" means a license that is listed
at https://creativecommons.org/compatiblelicenses that has been
approved by Creative Commons as being essentially equivalent to this
License, including, at a minimum, because that license: (i) contains
terms that have the same purpose, meaning and effect as the License
Elements of this License; and, (ii) explicitly permits the relicensing
of adaptations of works made available under that license under this
License or a Creative Commons jurisdiction license with the same
License Elements as this License.
d. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
e. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, ShareAlike.
f. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
g. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
h. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
i. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
j. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
k. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor waives the
exclusive right to collect such royalties for any exercise by You
of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(c), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(c), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under the
terms of: (i) this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
License. If you license the Adaptation under one of the licenses
mentioned in (iv), you must comply with the terms of that license. If
you license the Adaptation under the terms of any of the licenses
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
comply with the terms of the Applicable License generally and the
following provisions: (I) You must include a copy of, or the URI for,
the Applicable License with every copy of each Adaptation You
Distribute or Publicly Perform; (II) You may not offer or impose any
terms on the Adaptation that restrict the terms of the Applicable
License or the ability of the recipient of the Adaptation to exercise
the rights granted to that recipient under the terms of the Applicable
License; (III) You must keep intact all notices that refer to the
Applicable License and to the disclaimer of warranties with every copy
of the Work as included in the Adaptation You Distribute or Publicly
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
You may not impose any effective technological measures on the
Adaptation that restrict the ability of a recipient of the Adaptation
from You to exercise the rights granted to that recipient under the
terms of the Applicable License. This Section 4(b) applies to the
Adaptation as incorporated in a Collection, but this does not require
the Collection apart from the Adaptation itself to be made subject to
the terms of the Applicable License.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and (iv) , consistent with Ssection 3(b), in the case of an
Adaptation, a credit identifying the use of the Work in the Adaptation
(e.g., "French translation of the Work by Original Author," or
"Screenplay based on original Work by Original Author"). The credit
required by this Section 4(c) may be implemented in any reasonable
manner; provided, however, that in the case of a Adaptation or
Collection, at a minimum such credit will appear, if a credit for all
contributing authors of the Adaptation or Collection appears, then as
part of these credits and in a manner at least as prominent as the
credits for the other contributing authors. For the avoidance of
doubt, You may only use the credit required by this Section for the
purpose of attribution in the manner set out above and, by exercising
Your rights under this License, You may not implicitly or explicitly
assert or imply any connection with, sponsorship or endorsement by the
Original Author, Licensor and/or Attribution Parties, as appropriate,
of You or Your use of the Work, without the separate, express prior
written permission of the Original Author, Licensor and/or Attribution
Parties.
d. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of the License.
Creative Commons may be contacted at https://creativecommons.org/.
+27
View File
@@ -0,0 +1,27 @@
# 16x16 RPG characters `v3.0`
16x16px RPG character sprite sheet for up to down games.
This asset pack has been downloaded from https://route1rodent.itch.io/16x16-rpg-character-sprite-sheet
Adapted from opengameart.org 's "[NES-Style RPG Characters](https://opengameart.org/content/nes-style-rpg-characters)" and "[More NES-style RPG Characters](https://opengameart.org/content/more-nes-style-rpg-characters)", with additional content made by [@route1rodent](https://route1rodent.itch.io).
## License
16x16 RPG character sprite sheet (c) by @route1rodent
"16x16 RPG character sprite sheet" is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License (CC BY-SA 3.0).
You should have received a copy of the license along with this
work. If not, see http://creativecommons.org/licenses/by-sa/3.0/.
## Credits
Maintained by @route1rodent:
- itch.io: https://route1rodent.itch.io
- Twitter: https://twitter.com/route1rodent
- Github: https://github.com/itsjavi
- Blog: https://blog.itsjavi.com
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB