Доски приведены в соответствие с кодом; bug_list/bug_closed → BUGS_OPEN/BUGS_CLOSED

Доска отставала от кода на три задачи — планировать по ней было нельзя.
Сверка проведена грепом по исходникам, а не по записям:

- L4-MIRROR ЗАКРЫТА: шаги 1-5 сделаны и проверены пользователем в MAME
  (зеркало в атласе, постановка тайла, отражение, прыжок сквозь зеркало с
  рождением тени, левый клип тени).  Протокол с разбором решений — в архиве;
- L3-CHOMP и L3-SKEL закрыты ещё 2026-08-08/07 (коммиты dc0bd47, 4d4323f,
  db4106a, 1461ed5), на доске значились как предстоящие;
- тайлсет palace (шаг 2 levels_plan) в коде есть целиком — pop_bg_load(type),
  pal_*.atl, дворцовый wall_pattern, решётки 25-29 и в tile_table, и в
  коллизии (tile_is_floor совпадает с seg006:0628);
- в GUARD-PHYS остаток пересобран по факту: check_chomped_guard сделан,
  скелет в check_guard_fallout сделан, ветки ТЕНИ нет — она уехала в L5-SHADOW.

Приёмки: по решению пользователя уровни 1-4 приняты SMOKE-тестами, полные
обходы всех комнат делаются по готовности ВСЕХ уровней — L3-PASS/L4-PASS как
отдельные задачи отменены, вместо них политика приёмок в архиве.

Новая цель — уровень 5.  Инвентарь res2005.bin: НИ ОДНОГО нового тайла, всё
портировано на уровнях 1-4.  Единственная новая механика — спецсобытие «тень
крадёт зелье» (комната 24): заведена задача L5-SHADOW с портом по SDLPoP
(check_shadow / do_init_shad / do_auto_moves + shad_drink_move /
autocontrol_shadow_level5 + ветка тени в check_guard_fallout), включая
готовые константы и то, что у нас уже есть под это.

Заведён MIRROR-FG-STALE (низкий): place_mirror пишет тайл в данные уровня, но
не в снимок room_fg, по которому работает коллизия — если зеркало поставлено,
пока игрок В комнате 4, оно невидимо для коллизии (тень не родится).  В
обычном прохождении недостижимо: дверь выхода в другой комнате.  Записан
точный сценарий воспроизведения читом ROOMNAV и фикс на несколько строк.

Правило «в _OPEN только незакрытое» теперь выполняется буквально:
- bug_list.md → BUGS_OPEN.md, bug_closed.md → BUGS_CLOSED.md (ссылки
  обновлены во всех документах и в комментарии pop_trob.c);
- из TASKS_OPEN убраны блоки закрытых задач (L3-CHOMP, L3-SKEL, L3-PASS,
  L4-MIRROR, DRAW-CHAR), справка по связности комнат уехала в архив;
- из BUGS_OPEN убраны 8 строк таблицы закрытых багов, закрытый T-2 (уехал в
  BUGS_CLOSED) и раздел «уровень 3 — неначатые задачи» (обе записи закрыты);
  сводная таблица пересобрана по реально открытым записям.

Все внутренние ссылки проверены скриптом: битых якорей 0.  make size-check
OK (65 программ), tests-host 5/5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:10:10 +03:00
parent c0075c7754
commit 5348feb5f4
20 changed files with 488 additions and 654 deletions
+2 -2
View File
@@ -69,8 +69,8 @@ Princed для DAT v1.0 (контейнер/индекс/чек-сумма, ко
- `roomtest/`**активная разработка**: уровень 1 целиком (Kid, стражи, - `roomtest/`**активная разработка**: уровень 1 целиком (Kid, стражи,
ловушки, ворота, loose-полы). Свой `CLAUDE.md`; текущие задачи — ловушки, ворота, loose-полы). Свой `CLAUDE.md`; текущие задачи —
`roomtest/TASKS_OPEN.md` (закрытые с протоколами — `roomtest/TASKS_OPEN.md` (закрытые с протоколами —
`roomtest/TASKS_CLOSED.md`), открытые баги — `roomtest/bug_list.md`, `roomtest/TASKS_CLOSED.md`), открытые баги — `roomtest/BUGS_OPEN.md`,
закрытые с разбором корней — `roomtest/bug_closed.md`. закрытые с разбором корней — `roomtest/BUGS_CLOSED.md`.
- `poc/` — ранний proof-of-concept (снег/атлас/kbd_raw); ассеты в `poc/res/`. - `poc/` — ранний proof-of-concept (снег/атлас/kbd_raw); ассеты в `poc/res/`.
- `bgtest/`, `coltest/` — отдельные проверки фона/коллизии. - `bgtest/`, `coltest/` — отдельные проверки фона/коллизии.
- `toolchain/` — python-упаковщики ассетов + эталонные PNG (`1.1-2.png`). - `toolchain/` — python-упаковщики ассетов + эталонные PNG (`1.1-2.png`).
+1 -1
View File
@@ -463,7 +463,7 @@ tiny/small.
перерисовку тайлов; замер 2026-07-30 — типичный кадр ~371 К тактов перерисовку тайлов; замер 2026-07-30 — типичный кадр ~371 К тактов
(~86 % периода). Инструмент замера уже в коде: полосы бордюра `PROF()` (~86 % периода). Инструмент замера уже в коде: полосы бордюра `PROF()`
в `roomtest.c`. План выжимания — `../roomtest/TASKS_CLOSED.md` (CLIP-1) и в `roomtest.c`. План выжимания — `../roomtest/TASKS_CLOSED.md` (CLIP-1) и
`../roomtest/bug_list.md` (T-1/T-2). `../roomtest/BUGS_OPEN.md` (T-1/T-2).
3. ~~**Ёмкость атласа на актора**~~ — **закрыт**: Kid разложен на 28 3. ~~**Ёмкость атласа на актора**~~ — **закрыт**: Kid разложен на 28
атласов-страниц по 8 спрайтов (`pop_pack_kid.py`), страж — на 5; атласов-страниц по 8 спрайтов (`pop_pack_kid.py`), страж — на 5;
мульти-страничного формата `.atl` не потребовалось. Побочно мульти-страничного формата `.atl` не потребовалось. Побочно
+2 -2
View File
@@ -7,13 +7,13 @@
| Документ | О чём | | Документ | О чём |
|----------|-------| |----------|-------|
| [`../roomtest/TASKS_OPEN.md`](../roomtest/TASKS_OPEN.md) | **Что берётся в работу сейчас** (не в этой папке, но входная точка) | | [`../roomtest/TASKS_OPEN.md`](../roomtest/TASKS_OPEN.md) | **Что берётся в работу сейчас** (не в этой папке, но входная точка) |
| [`../roomtest/bug_list.md`](../roomtest/bug_list.md) | Открытые баги roomtest (закрытые — в `bug_closed.md` рядом) | | [`../roomtest/BUGS_OPEN.md`](../roomtest/BUGS_OPEN.md) | Открытые баги roomtest (закрытые — в `BUGS_CLOSED.md` рядом) |
| [`impl_diff.md`](impl_diff.md) | **Осознанные расхождения с SDLPoP**: где мы сделали не дословно и почему | | [`impl_diff.md`](impl_diff.md) | **Осознанные расхождения с SDLPoP**: где мы сделали не дословно и почему |
| [`perf_backlog.md`](perf_backlog.md) | **Отложенная оптимизация отрисовки** с замерами + как мерить (wait-state'ы, границы кадра) | | [`perf_backlog.md`](perf_backlog.md) | **Отложенная оптимизация отрисовки** с замерами + как мерить (wait-state'ы, границы кадра) |
| [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP | | [`levels_plan.md`](levels_plan.md) | Следующий этап: уровни 2+, второй тайлсет, читы SDLPoP |
| [`layout_plan_v2.md`](layout_plan_v2.md) | Раскладка кода по окнам/банкам/страницам + замеры скорости отрисовки | | [`layout_plan_v2.md`](layout_plan_v2.md) | Раскладка кода по окнам/банкам/страницам + замеры скорости отрисовки |
| [`room_model_plan.md`](room_model_plan.md) | `kid_room ≠ drawn_room` (straddle): сделан S1, остальное впереди | | [`room_model_plan.md`](room_model_plan.md) | `kid_room ≠ drawn_room` (straddle): сделан S1, остальное впереди |
| [`host_tests_plan.md`](host_tests_plan.md) | Модульные тесты движка под ucsim_z80: два шва, регрессии из `bug_closed.md`, дифф против SDLPoP | | [`host_tests_plan.md`](host_tests_plan.md) | Модульные тесты движка под ucsim_z80: два шва, регрессии из `BUGS_CLOSED.md`, дифф против SDLPoP |
| [`shadow_render.md`](shadow_render.md) | **Вид Тени (OR+XOR)** — отложено: почему XOR несовместим с прозрачностью `#FF`, замер подготовки источника, четыре варианта | | [`shadow_render.md`](shadow_render.md) | **Вид Тени (OR+XOR)** — отложено: почему XOR несовместим с прозрачностью `#FF`, замер подготовки источника, четыре варианта |
| [`ideas_backlog.md`](ideas_backlog.md) | Осознанно отложенные гипотезы (мышь, PRNG) | | [`ideas_backlog.md`](ideas_backlog.md) | Осознанно отложенные гипотезы (мышь, PRNG) |
| [`prng_alternatives.md`](prng_alternatives.md) | Запасные генераторы, если упрёмся в бюджет кадра | | [`prng_alternatives.md`](prng_alternatives.md) | Запасные генераторы, если упрёмся в бюджет кадра |
+3 -3
View File
@@ -51,9 +51,9 @@
Минимум, который надо перехватывать: `pop_set_redraw`, Минимум, который надо перехватывать: `pop_set_redraw`,
`pop_set_redraw_above`, `pop_loose_mob_spawn`, `pop_gate_redraw`. `pop_set_redraw_above`, `pop_loose_mob_spawn`, `pop_gate_redraw`.
## Фаза 2. Регрессионные кейсы из `bug_closed.md` ## Фаза 2. Регрессионные кейсы из `BUGS_CLOSED.md`
После швов `bug_closed.md` превращается в готовую спецификацию: у каждой После швов `BUGS_CLOSED.md` превращается в готовую спецификацию: у каждой
записи есть симптом и ожидаемое поведение. Кандидаты, которые ловятся записи есть симптом и ожидаемое поведение. Кандидаты, которые ловятся
логикой (без отрисовки и без железа): логикой (без отрисовки и без железа):
@@ -69,7 +69,7 @@
| `BUG-LOOSE-2` | кусок, начавший падать, долетает и кладёт щебень ПОСЛЕ смены комнаты | | `BUG-LOOSE-2` | кусок, начавший падать, долетает и кладёт щебень ПОСЛЕ смены комнаты |
| `BUG-CEIL-2` | loose-плита ряда 2 верхнего соседа живёт как «ряд −1» | | `BUG-CEIL-2` | loose-плита ряда 2 верхнего соседа живёт как «ряд −1» |
`BUG-LOOSE-2` стоит взять первым: он до сих пор помечен в `bug_list.md` `BUG-LOOSE-2` стоит взять первым: он до сих пор помечен в `BUGS_OPEN.md`
как непроверенный именно потому, что гонку «уйти из комнаты раньше, чем как непроверенный именно потому, что гонку «уйти из комнаты раньше, чем
долетит плита» через мост MAME воспроизвести не удалось. На уровне логики долетит плита» через мост MAME воспроизвести не удалось. На уровне логики
это несколько строк — заспавнить кусок, сменить комнату, тикать до это несколько строк — заспавнить кусок, сменить комнату, тикать до
+1 -1
View File
@@ -16,7 +16,7 @@
**Файлы:** `roomtest/pop_map.c` (`pop_coll_shift`, `pop_coll_invalidate`, **Файлы:** `roomtest/pop_map.c` (`pop_coll_shift`, `pop_coll_invalidate`,
`check_collisions`), `roomtest/roomtest.c` (`enter_room_side`). `check_collisions`), `roomtest/roomtest.c` (`enter_room_side`).
**Связанный баг:** BUG-GATE-PASS-1 (`roomtest/bug_closed.md`). **Связанный баг:** BUG-GATE-PASS-1 (`roomtest/BUGS_CLOSED.md`).
**Дата:** 2026-08-09. **Дата:** 2026-08-09.
### Как в оригинале ### Как в оригинале
+2 -2
View File
@@ -525,8 +525,8 @@ memory `mame_mcp_bridge`); в самом `roomtest.c` для этого уже
(до 7 блитов). Сгенерировать в атласе «столб решётки» и выводить одним (до 7 блитов). Сгенерировать в атласе «столб решётки» и выводить одним
`gfx_blit_part` с обрезкой по фазе `gate_bot_y & 7`: 7 блитов → 1. `gfx_blit_part` с обрезкой по фазе `gate_bot_y & 7`: 7 блитов → 1.
3. **Не перерисовывать статичные части шва** — грань ворот, пол и кромка при 3. **Не перерисовывать статичные части шва** — грань ворот, пол и кромка при
анимации решётки не меняются (см. OPT-1 в `../roomtest/bug_closed.md` анимации решётки не меняются (см. OPT-1 в `../roomtest/BUGS_CLOSED.md`
решено не делать, стоимость транзиентная). решено не делать, стоимость транзиентная).
4. **T-1 / T-2** (`../roomtest/bug_list.md`) — перерисовка пик по причине и 4. **T-1 / T-2** (`../roomtest/BUGS_OPEN.md`) — перерисовка пик по причине и
idle-skip Кида: самый большой оставшийся резерв, потому что убирает работу idle-skip Кида: самый большой оставшийся резерв, потому что убирает работу
целиком, а не удешевляет её. целиком, а не удешевляет её.
+15 -3
View File
@@ -44,8 +44,8 @@ up_pressed/go_up_leveldoor`, `seg006.c: play_seq → SEQ_END_LEVEL`,
| 1 | dungeon | обычный | — (текущая база) | | 1 | dungeon | обычный | — (текущая база) |
| **2** | **dungeon** | **обычный** | **ничего нового: тот же набор объектов минус меч** | | **2** | **dungeon** | **обычный** | **ничего нового: тот же набор объектов минус меч** |
| 3 | dungeon | СКЕЛЕТ | чомперы | | 3 | dungeon | СКЕЛЕТ | чомперы |
| 4 | palace | обычный | **тайлсет palace**, зеркало (спецсобытие `mirror_level`) | | 4 | palace | обычный | **тайлсет palace**, зеркало (спецсобытие `mirror_level`) — **СДЕЛАНО** |
| 5 | palace | обычный | | | 5 | palace | обычный | новых ТАЙЛОВ нет; спецсобытие **тень крадёт зелье** (комната 24) — [L5-SHADOW](../roomtest/TASKS_OPEN.md#l5-shadow) |
| 6 | palace | ТОЛСТЫЙ | падение на входе (спецсобытие) | | 6 | palace | ТОЛСТЫЙ | падение на входе (спецсобытие) |
| 7 | dungeon | обычный | — | | 7 | dungeon | обычный | — |
| 8, 9 | dungeon | обычный | — | | 8, 9 | dungeon | обычный | — |
@@ -111,7 +111,19 @@ pop_current_level) → load_level(pop_next_level)`. Порядок сноса/
--- ---
## 3. Шаг 2 — второй тайлсет (palace, уровни 4+) ## 3. Шаг 2 — второй тайлсет (palace, уровни 4+) — **СДЕЛАН**
> **Закрыт (ревизия 2026-08-11 по коду).** В дереве есть всё, что
> проектировалось ниже: `pop_pack_bg.py` печёт ДВА набора атласов
> (`pop_*` из VDUNGEON, `pal_*` из VPALACE, каскад с обменом приоритетов),
> `pop_bg_load` принимает тип набора и ставит флаг `pop_palace`, по которому
> `pop_room.c` выбирает дворцовую ветку (`wall_pattern` дворца — сплошная
> заливка + пять mono-полос, `PALACE_WALL_MONO_IDS`), решётчатые тайлы
> 25-29 есть и в `tile_table` (`pop_tile.c`), и в коллизии — `tile_is_floor`
> совпадает с `seg006:0628` тайл в тайл. Уровень 4 проходится smoke-тестом.
>
> Текст ниже оставлен как справочник по раскрою палитры и по тому, почему
> смена тайлсета — это перезапись 32 записей, а не переиндексация спрайтов.
**Ассеты.** `toolchain/pop_pack_bg.py` уже читает PNG каскадом **Ассеты.** `toolchain/pop_pack_bg.py` уже читает PNG каскадом
VDUNGEON→VPALACE (та же логика, что в игре), но печёт ОДИН набор атласов VDUNGEON→VPALACE (та же логика, что в игре), но печёт ОДИН набор атласов
+2 -2
View File
@@ -9,7 +9,7 @@
> - **S2/S3/S4 — не сделаны и не срочны.** Исходный повод (баг #4, > - **S2/S3/S4 — не сделаны и не срочны.** Исходный повод (баг #4,
> пинг-понг у шва) закрыт иначе — поправкой odd-pixel в > пинг-понг у шва) закрыт иначе — поправкой odd-pixel в
> `char_x_forward_edge` + `pop_leave_timer` (разбор корня — > `char_x_forward_edge` + `pop_leave_timer` (разбор корня —
> `../roomtest/bug_closed.md`, BUG-SEAM-PINGPONG). > `../roomtest/BUGS_CLOSED.md`, BUG-SEAM-PINGPONG).
> >
> **Зачем документ остаётся.** Полная straddle-модель понадобится для: > **Зачем документ остаётся.** Полная straddle-модель понадобится для:
> (а) читов осмотра соседних комнат `H/J/U/N` (`levels_plan.md` §4), > (а) читов осмотра соседних комнат `H/J/U/N` (`levels_plan.md` §4),
@@ -78,7 +78,7 @@ drawn_room с `curr_col=-1/10` + снапшоты соседей `g_lcol/g_rcol`
### S4. Полировка ### S4. Полировка
- Окклюзия/ceiling у шва при straddle, BUG-OCCL-1 (глубина), правый край. - Окклюзия/ceiling у шва при straddle, BUG-OCCL-1 (глубина), правый край.
## Связанные баги — все ЗАКРЫТЫ (`../roomtest/bug_closed.md`) ## Связанные баги — все ЗАКРЫТЫ (`../roomtest/BUGS_CLOSED.md`)
BUG-CEIL-1 (руки при прыжке вверх), BUG-CEIL-2 (loose в потолке), BUG-CEIL-1 (руки при прыжке вверх), BUG-CEIL-2 (loose в потолке),
BUG-CEIL-3 (потолок над анимируемыми воротами), BUG-OCCL-1 (тень дальней BUG-CEIL-3 (потолок над анимируемыми воротами), BUG-OCCL-1 (тень дальней
колонны) — починены без полной straddle-модели. То есть S4 «полировка колонны) — починены без полной straddle-модели. То есть S4 «полировка
@@ -2,7 +2,7 @@
Сюда переезжает всё, что **закрыто**: подтверждённые фиксы, снятые Сюда переезжает всё, что **закрыто**: подтверждённые фиксы, снятые
диагнозы, осознанные решения «не делать». Открытые баги — в диагнозы, осознанные решения «не делать». Открытые баги — в
[`bug_list.md`](bug_list.md), текущие задачи — в [`BUGS_OPEN.md`](BUGS_OPEN.md), текущие задачи — в
[`TASKS_OPEN.md`](TASKS_OPEN.md), закрытые задачи с протоколами — в [`TASKS_OPEN.md`](TASKS_OPEN.md), закрытые задачи с протоколами — в
[`TASKS_CLOSED.md`](TASKS_CLOSED.md). [`TASKS_CLOSED.md`](TASKS_CLOSED.md).
@@ -377,7 +377,7 @@ kid_col = 8 guard_col = 12
> **Итог 2026-08-05.** Физика тут ни при чём — виновата клавиатура. > **Итог 2026-08-05.** Физика тут ни при чём — виновата клавиатура.
> Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам > Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам
> 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и > 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и
> фикс — [BUG-KBD-5](bug_closed.md#bug-kbd-5); поведение Shift в MAME > фикс — [BUG-KBD-5](BUGS_CLOSED.md#bug-kbd-5); поведение Shift в MAME
> проверено замером карты `_kbdraw_down`. Осталось подтвердить сам зацеп > проверено замером карты `_kbdraw_down`. Осталось подтвердить сам зацеп
> живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет. > живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет.
@@ -426,7 +426,7 @@ kid_col = 8 guard_col = 12
Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у
нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4). нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4).
Замер в MAME: при зажатом Shift и зажатой стрелке бит `LSh` в Замер в MAME: при зажатом Shift и зажатой стрелке бит `LSh` в
`_kbdraw_down` стоял в нуле. Разбор — [BUG-KBD-5](bug_closed.md#bug-kbd-5). `_kbdraw_down` стоял в нуле. Разбор — [BUG-KBD-5](BUGS_CLOSED.md#bug-kbd-5).
- **Сцена харнесса не равна комнате 9.** Там изолированная комната - **Сцена харнесса не равна комнате 9.** Там изолированная комната
(соседи — стена), а в игре слева комната 8; кромки шва участвуют в (соседи — стена), а в игре слева комната 8; кромки шва участвуют в
`get_tile`. Проверять чтением `Kid.x` в момент прыжка: попал ли он в `get_tile`. Проверять чтением `Kid.x` в момент прыжка: попал ли он в
@@ -1450,7 +1450,7 @@ MAME по кромкам.
**Оговорка о полноте проверки.** Каждый корень закрыт тем, что его СОБСТВЕННЫЙ **Оговорка о полноте проверки.** Каждый корень закрыт тем, что его СОБСТВЕННЫЙ
сценарий больше не воспроизводится; сквозного прохождения уровня и поиска сценарий больше не воспроизводится; сквозного прохождения уровня и поиска
регрессий в соседней механике автоматика не делала. Чек-лист для ручной регрессий в соседней механике автоматика не делала. Чек-лист для ручной
перепроверки — в [`bug_list.md`](bug_list.md), раздел «Ручная перепроверка перепроверки — в [`BUGS_OPEN.md`](BUGS_OPEN.md), раздел «Ручная перепроверка
фиксов». Особое внимание — порту `check_collisions`: он переписал ВСЮ фиксов». Особое внимание — порту `check_collisions`: он переписал ВСЮ
горизонтальную коллизию. горизонтальную коллизию.
@@ -1545,7 +1545,7 @@ foretable — в той же странице по смещению `0x1000` (с
**Остаток:** экран при этом перелистывается на соседнюю комнату, и Кид в шве **Остаток:** экран при этом перелистывается на соседнюю комнату, и Кид в шве
не рисуется — отдельный баг BUG-SEAM-DRAW-1 (модель straddle S3), см. не рисуется — отдельный баг BUG-SEAM-DRAW-1 (модель straddle S3), см.
`bug_list.md`. `BUGS_OPEN.md`.
### BUG-STANDUP-1. Вставание из приседа у стены роняло сквозь пол — **ЗАКРЫТ** ### BUG-STANDUP-1. Вставание из приседа у стены роняло сквозь пол — **ЗАКРЫТ**
@@ -1887,10 +1887,10 @@ dir_56_none`. То есть стражи — такая же часть дан
# Прогон всех комнат уровней 1 и 2 (пользователь, 2026-08-07) — ЗАКРЫЛ ТРИ РЕВИЗИИ РАЗОМ # Прогон всех комнат уровней 1 и 2 (пользователь, 2026-08-07) — ЗАКРЫЛ ТРИ РЕВИЗИИ РАЗОМ
Результат прогона: **крупных багов нет**. Тем самым закрыты и переехали сюда Результат прогона: **крупных багов нет**. Тем самым закрыты и переехали сюда
из `bug_list.md` три накопившихся хвоста — чек-листы ручной перепроверки из `BUGS_OPEN.md` три накопившихся хвоста — чек-листы ручной перепроверки
фиксов, таблица обхода 24 комнат уровня 1 и таблица сырых наблюдений прогона фиксов, таблица обхода 24 комнат уровня 1 и таблица сырых наблюдений прогона
2026-08-03. Всё, что с прогона 2026-08-07 осталось открытым, — три записи 2026-08-03. Всё, что с прогона 2026-08-07 осталось открытым, — три записи
уровня 2 в [`bug_list.md`](bug_list.md) (BUG-GUARD-COLOR-1, уровня 2 в [`BUGS_OPEN.md`](BUGS_OPEN.md) (BUG-GUARD-COLOR-1,
BUG-GUARD-SPLASH-1, BUG-CHEAT-FIGHT-1); ни одна из них не мешает играть. BUG-GUARD-SPLASH-1, BUG-CHEAT-FIGHT-1); ни одна из них не мешает играть.
Сырые формулировки пользователя лежат рядом: [`bugs_level1.md`](bugs_level1.md) Сырые формулировки пользователя лежат рядом: [`bugs_level1.md`](bugs_level1.md)
@@ -2040,7 +2040,7 @@ pop_guard_hurt = 0;`), а не безусловно — это и экономи
2407 → 3369 (+962 Б, банк занят на 20.6 %, свободно 13 015 Б). 2407 → 3369 (+962 Б, банк занят на 20.6 %, свободно 13 015 Б).
**Цвет брызг** зависит от палитры стража (слоты `0x90..0x9F`), поэтому он **Цвет брызг** зависит от палитры стража (слоты `0x90..0x9F`), поэтому он
изменится вместе с [BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1) — изменится вместе с [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1) —
отдельной работы не требует. отдельной работы не требует.
@@ -2141,7 +2141,7 @@ if (curr_row_coll_room[column] >= 0 &&
Расхождение с дословным вариантом A записано в `../docs/impl_diff.md` (D-1). Расхождение с дословным вариантом A записано в `../docs/impl_diff.md` (D-1).
**Осторожно:** это сердце коллизии, вокруг которого разбирался **Осторожно:** это сердце коллизии, вокруг которого разбирался
BUG-SEAM-PINGPONG (см. `bug_closed.md`). Приёмка: BUG-SEAM-PINGPONG (см. `BUGS_CLOSED.md`). Приёмка:
1. Комнаты 6 ↔ 8 уровня 1, закрытая решётка, **обе** стороны. 1. Комнаты 6 ↔ 8 уровня 1, закрытая решётка, **обе** стороны.
2. Мелким шагом (упереться) И с разбега (не пройти насквозь) — сценарий 2. Мелким шагом (упереться) И с разбега (не пройти насквозь) — сценарий
@@ -2252,5 +2252,25 @@ draw_tile_anim(); <- СВОЯ графика тайла: челюсти
чомпера. Безусловная пометка = перерисовка тайла каждый кадр на каждый чомпера. Безусловная пометка = перерисовка тайла каждый кадр на каждый
факел; слой `draw_tile_anim` рисует ещё пики, зелье и меч (seg008:0644), факел; слой `draw_tile_anim` рисует ещё пики, зелье и меч (seg008:0644),
поэтому теоретически «застыть под пламенем» могут и они — заведено открытым поэтому теоретически «застыть под пламенем» могут и они — заведено открытым
в `bug_list.md` (TORCH-ANIM-RIGHT). На уровнях 1–4 такого соседства не в `BUGS_OPEN.md` (TORCH-ANIM-RIGHT). На уровнях 1–4 такого соседства не
встретилось. встретилось.
---
<a id="t-2"></a>
## T-2. Idle-skip — **ЗАКРЫТ 2026-08-08**
Сделан как шаг 1 задачи [DRAW-COST](TASKS_OPEN.md#draw-cost) (коммит
`a25ce58`), и шире, чем формулировался здесь: пропускается не только Кид, а
ЛЮБОЙ персонаж, у которого с прошлой отрисовки этой страницы дабл-буфера не
изменился ни один вход отрисовки, — включая труп стража и ждущего стража.
Условие «обе страницы уже получили это состояние», которого требовала эта
запись, выполнено само собой: снимок входов хранится ПО СТРАНИЦАМ.
Замер: комната 1.3 с трупом стража, Кид стоит — 210 % -> 116 % кадрового
периода, ноль вызовов `pop_heal_fast` за кадр. Контракт — шапка
`pop_cdraw.h`, разбор и что делать дальше — `TASKS_OPEN.md#draw-cost`.
Связь с T-1 сработала как и предсказано: пока Кида не перерисовываем, heal'а
нет, стирать пики нечем. Но T-1 остаётся открытым — трогать пики
безусловно мы всё ещё продолжаем, когда Кид рядом ДВИЖЕТСЯ.
@@ -1,56 +1,37 @@
# roomtest — ОТКРЫТЫЕ баги и незакрытые оптимизации # roomtest — ОТКРЫТЫЕ баги и незакрытые оптимизации
Только то, что **не закрыто**. Всё закрытое (и, что важнее, разбор корней) **Здесь ТОЛЬКО незакрытое.** Всё закрытое (и, что важнее, разбор корней)
переехало в [`bug_closed.md`](bug_closed.md): прежде чем заводить новый баг, живёт в [`BUGS_CLOSED.md`](BUGS_CLOSED.md) — прежде чем заводить новый баг,
грепни там по симптому. грепни там по симптому. Сырые формулировки пользователя с прогонов —
[`bugs_level1.md`](bugs_level1.md) / [`bugs_level2.md`](bugs_level2.md).
Приоритеты работ — в [`TASKS_OPEN.md`](TASKS_OPEN.md) (закрытые задачи с Приоритеты работ — в [`TASKS_OPEN.md`](TASKS_OPEN.md) (закрытые задачи с
протоколами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md)), а не здесь. Правило протоколами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md)), а не здесь. Правило
проекта: механику сверять с `../SDLPoP/src/` ДО кодинга. проекта: механику сверять с `../SDLPoP/src/` ДО кодинга.
**Ревизия списка: 2026-08-07 — прогон ВСЕХ комнат уровней 1 и 2** **Ревизия 2026-08-11:** файл вычищен от закрытых записей (правило «в `_OPEN`
(пользователь). Крупных багов нет. Поэтому в [`bug_closed.md`](bug_closed.md) только открытое»). Уровни 1-4 приняты smoke-тестами; крупных багов нет.
уехали разом: чек-листы ручной перепроверки фиксов (2026-08-03, обе волны),
таблица обхода 24 комнат уровня 1 и таблица сырых наблюдений того же прогона —
все они закрыты этим проходом. С прогона пришли **три новые записи**, все по
уровню 2 (сырые формулировки — [`bugs_level2.md`](bugs_level2.md)).
| ID | что | тип | статус | | ID | что | тип | статус |
|----|-----|-----|--------| |----|-----|-----|--------|
| ~~BUG-CHEAT-FIGHT-1~~ | `+`/`` в бою с вынутым мечом → Кид теряет управление | Major (чит) | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-cheat-fight-1) | | [FORE-DUP](#fore-dup) | передний слой тайла рисуется дважды при перекрытии объектов | оптимизация | открыт: **сначала замерить**, потом чинить |
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводимый: ни сценарием, ни попиксельной подгонкой X не поднимается | | [MIRROR-FG-STALE](#mirror-fg-stale) | зеркало поставлено, пока игрок В комнате 4 → коллизия его не видит | **низкий** (в обычном прохождении недостижимо) | открыт, фикс на несколько строк |
| [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводимый: на повторе не поднялся; на пререлиз |
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
| ~~T-2~~ | Кид перерисовывается в покое | оптимизация | **ЗАКРЫТ 2026-08-08** — [DRAW-COST шаг 1](TASKS_OPEN.md#draw-cost) |
| ~~BUG-GATE-PASS-1~~ | проход сквозь закрытую решётку шва | Major | **ЗАКРЫТ 2026-08-09** — [bug_closed.md](bug_closed.md#bug-gate-pass-1), смоук уровня 1 пройден |
| ~~BUG-GUARD-IX-1~~ | «зависание» в бою со стражем: затёрт IX главного цикла | Blocker | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-guard-ix-1) |
| ~~BUG-GATE-SEAM-ROW1~~ | решётка в шве не анимируется, если ворота не в ряду 0 | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-gate-seam-row1) |
| ~~BUG-LOOSE-BUTTON-1~~ | упавшая плита не нажимает кнопку под собой | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-loose-button-1) |
| ~~BUG-GATE-FF-1~~ | ворота «открыты навсегда» непроходимы (0xFF перегружен) | Major | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-gate-ff-1) |
| ~~BUG-TORCH-CHOMP-1~~ | чомпер стирает пламя соседнего факела | Minor | **ЗАКРЫТ 2026-08-10** — [bug_closed.md](bug_closed.md#bug-torch-chomp-1) |
| [DIED-ON-BUTTON](#died-on-button) | `died_on_button` (seg007:776) не портирован | порт | открыт | | [DIED-ON-BUTTON](#died-on-button) | `died_on_button` (seg007:776) не портирован | порт | открыт |
| [TORCH-ANIM-RIGHT](#torch-anim-right) | под запечённым пламенем застывают не только челюсти чомпера | **низкий** | открыт: на уровнях 1-4 такого соседства нет |
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводим: ни сценарием, ни попиксельной подгонкой X не поднимается |
| [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводим: на повторе не поднялся; на пререлиз |
--- ---
<a id="уровень-2"></a> <a id="уровень-2"></a>
# Уровень 2 — баги с приёмки # С приёмки уровня 2 (2026-08-07)
Приёмка уровня 2 закрыта ([L2-PASS](TASKS_CLOSED.md#l2-pass)): smoke Всё найденное тем прогоном закрыто, кроме двух записей ниже
2026-08-05 + обход всех комнат 2026-08-07. Карта содержимого уровня (что где (`BUG-SPIKE-1` — уровень 2, комната 6). Карта содержимого уровня 2 (что где
стоит по `res2002.bin`, какие кнопки какие ворота открывают) — там же, по ней стоит по `res2002.bin`, какие кнопки какие ворота открывают) — в
видно, «механика не сработала» это или «так и задумано». [`TASKS_CLOSED.md`](TASKS_CLOSED.md#l2-pass), по ней видно, «механика не
сработала» это или «так и задумано».
**Закрыто с этой волны:** [BUG-LOOSE-3](bug_closed.md) — чёрный бар под
упавшей плитой-потолком; [BUG-GUARD-DEAF-1](bug_closed.md) — страж не
оборачивался на вернувшегося Кида;
[BUG-GUARD-SPLASH-1](bug_closed.md#bug-guard-splash-1) — «брызги» при
попадании по стражу; [BUG-SWORD-GHOST-1](bug_closed.md#bug-sword-ghost-1) —
меч, спрятанный посреди боя после перехода комнаты (корень — мнимая стена за
краем комнаты в `get_tile`; кэш соседей расширен до полных комнат);
[BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1) — цвет стража и его
полосы HP теперь берётся из `guards_color` уровня (2026-08-07).
Ниже — то, что осталось открытым после прогона 2026-08-07.
<a id="bug-spike-1"></a> <a id="bug-spike-1"></a>
## BUG-SPIKE-1. Пики залипают выдвинутыми — НИЗКИЙ ПРИОРИТЕТ, МАЛОВОСПРОИЗВОДИМ ## BUG-SPIKE-1. Пики залипают выдвинутыми — НИЗКИЙ ПРИОРИТЕТ, МАЛОВОСПРОИЗВОДИМ
@@ -141,20 +122,6 @@ check_spiked (seg006:0658): убивает при h>=2 на кадрах б
--- ---
<a id="уровень-3"></a> <a id="уровень-3"></a>
# Уровень 3 — не баги, а неначатые задачи
Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал четыре наблюдения. Два
были настоящими багами и **закрыты в тот же день** (разбор — в
[`bug_closed.md`](bug_closed.md): **BUG-JUMPWALL-1** и **BUG-SEAM-WEDGE-1**).
Оставшиеся два — не баги, а неначатые задачи:
| наблюдение | что это на самом деле |
|------------|------------------------|
| к.22: чомпер (2,6) не анимируется и **вообще не рисуется** | [L3-CHOMP](TASKS_OPEN.md#l3-chomp) — механики чомперов НЕТ. В таблице тайлов `pop_bg.c:211` строка `12 chomper` рисует только основание, правую грань и низ; самих челюстей (спрайт из `chtab`, `draw_tile_anim` seg008) нет вовсе. Так и должно выглядеть до порта |
| к.10: скелет не оживает | [L3-SKEL](TASKS_OPEN.md#l3-skel) — `check_skel` (seg002:0E1F) не портирован. **И комната другая:** тайл `skeleton(21)`, который оживает, лежит в **к.1 (1,5)** — это `skeleton_room=1, skeleton_column=5, skeleton_row=1`. Ещё два скелета уровня (к.17 (2,7), к.19 (2,2)) — просто декорация, они не оживают никогда. Плюс условие: скелет встаёт, только когда **дверь уровня уже открыта** и Кид стоит в колонке 2 или 3 |
Приёмки уровня 3 (полного обхода комнат) ещё не было — она осмысленна только
после L3-CHOMP/L3-SKEL.
--- ---
@@ -193,22 +160,6 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал
**Приоритет:** отдаётся почти бесплатно ПОСЛЕ T-2, отдельно не окупается. **Приоритет:** отдаётся почти бесплатно ПОСЛЕ T-2, отдельно не окупается.
<a id="t-2"></a> <a id="t-2"></a>
## T-2. Idle-skip — **ЗАКРЫТ 2026-08-08**
Сделан как шаг 1 задачи [DRAW-COST](TASKS_OPEN.md#draw-cost) (коммит
`a25ce58`), и шире, чем формулировался здесь: пропускается не только Кид, а
ЛЮБОЙ персонаж, у которого с прошлой отрисовки этой страницы дабл-буфера не
изменился ни один вход отрисовки, — включая труп стража и ждущего стража.
Условие «обе страницы уже получили это состояние», которого требовала эта
запись, выполнено само собой: снимок входов хранится ПО СТРАНИЦАМ.
Замер: комната 1.3 с трупом стража, Кид стоит — 210 % -> 116 % кадрового
периода, ноль вызовов `pop_heal_fast` за кадр. Контракт — шапка
`pop_cdraw.h`, разбор и что делать дальше — `TASKS_OPEN.md#draw-cost`.
Связь с T-1 сработала как и предсказано: пока Кида не перерисовываем, heal'а
нет, стирать пики нечем. Но T-1 остаётся открытым — трогать пики
безусловно мы всё ещё продолжаем, когда Кид рядом ДВИЖЕТСЯ.
--- ---
@@ -272,9 +223,9 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал
- **ROOMNAV (`=`/`-`) — тоже наш чит**, которого в оригинале не было, как и - **ROOMNAV (`=`/`-`) — тоже наш чит**, которого в оригинале не было, как и
`S`. Все они со временем съедутся в общий блок читов, разрешаемый в `S`. Все они со временем съедутся в общий блок читов, разрешаемый в
настройках; пока просто включены (`pop_cheats = 1` в `roomtest.c`). настройках; пока просто включены (`pop_cheats = 1` в `roomtest.c`).
Известный баг этого чита закрыт — [BUG-CHEAT-FIGHT-1](bug_closed.md#bug-cheat-fight-1). Известный баг этого чита закрыт — [BUG-CHEAT-FIGHT-1](BUGS_CLOSED.md#bug-cheat-fight-1).
- Комнаты **13, 18, 24 уровня 1 недостижимы** в обычной игре — это свойство - Комнаты **13, 18, 24 уровня 1 недостижимы** в обычной игре — это свойство
данных уровня (разбор — «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md)); данных уровня (разбор — «НЕ БАГИ» в [`BUGS_CLOSED.md`](BUGS_CLOSED.md));
приоритет багов в них низкий. Аналогично 23/24 на уровне 3. приоритет багов в них низкий. Аналогично 23/24 на уровне 3.
@@ -283,7 +234,7 @@ Smoke-прогон уровня 3 (пользователь, 2026-08-05) дал
<a id="died-on-button"></a> <a id="died-on-button"></a>
## DIED-ON-BUTTON. `died_on_button` (seg007:776) не портирован ## DIED-ON-BUTTON. `died_on_button` (seg007:776) не портирован
Обнаружено при разборе [BUG-LOOSE-BUTTON-1](bug_closed.md#bug-loose-button-1). Обнаружено при разборе [BUG-LOOSE-BUTTON-1](BUGS_CLOSED.md#bug-loose-button-1).
`check_press` (seg006:1707) разбирает ЛЮБОГО мёртвого `Char`, не только Кида: `check_press` (seg006:1707) разбирает ЛЮБОГО мёртвого `Char`, не только Кида:
```c ```c
@@ -306,10 +257,11 @@ if (curr_tile2 == tiles_15_opener || curr_tile2 == tiles_6_closer) {
--- ---
<a id="torch-anim-right"></a>
## TORCH-ANIM-RIGHT. Под пламенем могут застыть не только челюсти чомпера ## TORCH-ANIM-RIGHT. Под пламенем могут застыть не только челюсти чомпера
Открыто 2026-08-10 при закрытии Открыто 2026-08-10 при закрытии
[BUG-TORCH-CHOMP-2](bug_closed.md#bug-torch-chomp-2). [BUG-TORCH-CHOMP-2](BUGS_CLOSED.md).
Пламя факела запекается в фон и рисуется в ячейке ПРАВОГО СОСЕДА. Оригинал Пламя факела запекается в фон и рисуется в ячейке ПРАВОГО СОСЕДА. Оригинал
после каждого кадра факела метит этого соседа (`set_redraw_anim_right`, после каждого кадра факела метит этого соседа (`set_redraw_anim_right`,
@@ -332,6 +284,7 @@ seg007:0101) и перерисовывает весь его слой `anim` п
--- ---
<a id="fore-dup"></a>
## FORE-DUP. Передний слой тайла рисуется ДВАЖДЫ при перекрытии объектов ## FORE-DUP. Передний слой тайла рисуется ДВАЖДЫ при перекрытии объектов
Найдено пользователем 2026-08-10 (вопросом, а не по симптому — картинка Найдено пользователем 2026-08-10 (вопросом, а не по симптому — картинка
@@ -381,3 +334,40 @@ for (tile_row = x_top_row; tile_row <= char_bottom_row; ++tile_row)
счётчиком, сцена «бой в комнате 3 уровня 1» и «Кид на тайле зеркала, счётчиком, сцена «бой в комнате 3 уровня 1» и «Кид на тайле зеркала,
ур. 4»; сравнить число нарисованных кусков с числом уникальных тайлов. ур. 4»; сравнить число нарисованных кусков с числом уникальных тайлов.
Если дубль мал — оставить как есть и закрыть запись. Если дубль мал — оставить как есть и закрыть запись.
---
<a id="mirror-fg-stale"></a>
## MIRROR-FG-STALE. Зеркало, поставленное при игроке В комнате 4, не попадает в коллизию
Заведено 2026-08-11 как краевой случай, оставшийся открытым от
[L4-MIRROR](TASKS_CLOSED.md#l4-mirror) (шаг 2). **Низкий приоритет: в
обычном прохождении недостижимо** — дверь выхода уровня 4 стоит не в той
комнате, где зеркало, поэтому игрок физически не может быть в комнате 4 в
момент её открытия.
**Причина (по коду, не по симптому).** `place_mirror` (`pop_trob.c`) пишет
тайл зеркала в ДАННЫЕ УРОВНЯ (`pop_level_set_tile` — страница уровня в W0) и,
если комната зеркала на экране, помечает тайл на перерисовку. А коллизия
работает не с данными уровня, а со СНИМКОМ комнаты `room_fg`, который
`pop_room_load` делает один раз при входе в комнату (`pop_map_set(room_fg)`
в `roomtest.c`). Снимок в этот момент не обновляется.
**Что будет:** зеркало нарисуется (перерисовка тайла отработает), но для
коллизии его как бы нет — `wall_type` не вернёт «стена слева», а ветка
`is_obstacle` для прыжка сквозь зеркало не сработает. То есть Кид пробежит
сквозь зеркало насквозь и **тень не родится**, пока комнату не перезайти.
**Сценарий проверки** (нужен чит `+`/`` ROOMNAV, иначе не собрать):
1. уровень 4, нажать кнопку, открывающую дверь выхода;
2. **пока дверь анимируется** (43 тика ≈ 3.5 с) уйти читом в комнату 4;
3. дождаться, когда дверь дорисует открытие — зеркало появится на экране;
4. разбежаться справа налево и прыгнуть в зеркало.
- Ожидание при баге: Кид пролетает насквозь как через пустоту, тени нет.
- После выхода из комнаты и возврата в неё всё работает нормально.
**Фикс — несколько строк:** в `place_mirror`, в ветке
`cur_room == MIRROR_ROOM`, обновить и живую карту, а не только данные уровня
`pop_map.c` уже есть внутренние точки записи `g_fg[tilepos] = ...`,
нужна публичная «поставить тайл в текущей комнате»). Делать вместе с любой
следующей правкой `pop_trob.c` — отдельного захода не стоит.
+2 -2
View File
@@ -18,13 +18,13 @@
**Что в работе сейчас — `TASKS_OPEN.md`** (доска задач: приоритеты, критерии **Что в работе сейчас — `TASKS_OPEN.md`** (доска задач: приоритеты, критерии
готовности); сделанное с протоколами замеров — `TASKS_CLOSED.md`. Баги — готовности); сделанное с протоколами замеров — `TASKS_CLOSED.md`. Баги —
`bug_list.md` (только ОТКРЫТЫЕ) и `bug_closed.md` (закрытые + разбор корней: `BUGS_OPEN.md` (только ОТКРЫТЫЕ) и `BUGS_CLOSED.md` (закрытые + разбор корней:
перед заведением нового бага грепни там по симптому); сырые формулировки перед заведением нового бага грепни там по симптому); сырые формулировки
пользователя с прогонов — `bugs_level1.md` / `bugs_level2.md`. План пользователя с прогонов — `bugs_level1.md` / `bugs_level2.md`. План
следующих уровней — `../docs/levels_plan.md`. следующих уровней — `../docs/levels_plan.md`.
Правило разнесения: как только задача/баг закрыт — запись целиком переезжает Правило разнесения: как только задача/баг закрыт — запись целиком переезжает
в `TASKS_CLOSED.md` / `bug_closed.md`, а в открытом файле остаётся ссылка. в `TASKS_CLOSED.md` / `BUGS_CLOSED.md`, а в открытом файле остаётся ссылка.
Открытые файлы обязаны читаться целиком за раз. Открытые файлы обязаны читаться целиком за раз.
## Сборка и запуск ## Сборка и запуск
+87 -146
View File
@@ -1,173 +1,114 @@
# Точка входа для следующей сессии (записано 2026-08-10, поздний вечер) # Точка входа для следующей сессии (обновлено 2026-08-11)
Файл для старта с чистого контекста: где всё стоит, что делать первым, какие Файл для старта с чистого контекста: где всё стоит, что делать первым, какие
грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам: грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам:
`TASKS_OPEN.md` (доска), `bug_list.md` (открытые баги), `CLAUDE.md`. `TASKS_OPEN.md` (доска), `BUGS_OPEN.md` (открытые баги), `CLAUDE.md`.
--- ---
## 1. Состояние репозитория ## 1. СЛЕДУЮЩАЯ ЗАДАЧА: [L5-SHADOW](TASKS_OPEN.md#l5-shadow) — уровень 5
Всё закоммичено и запушено, `main` в синхроне с `origin/main`. Уровни 1-4 приняты **smoke-тестами**; полные обходы всех комнат делаются по
Последний коммит сессии — `8beb66a`. готовности ВСЕХ уровней ([политика приёмок](TASKS_CLOSED.md#pass-policy),
решение пользователя 2026-08-11) — отдельных `L3-PASS`/`L4-PASS` больше нет.
`tests-host`: 5 наборов, все проходят (`[geom] 3144`). Уровень 5 **не приносит ни одного нового тайла** (инвентарь `res2005.bin`
`make size-check`: чисто, роста нет. сверен: всё уже есть с уровней 1-4). Вся работа — одно спецсобытие: тень
появляется в комнате 24, ждёт открытия двери, идёт к зелью, **выпивает его**
и уходит за левый край; боя нет. Писать: `do_auto_moves` + таблица
`shad_drink_move`, `do_init_shad`, ветку `check_shadow` для уровня 5,
`autocontrol_shadow_level5`, плюс ветку ТЕНИ в `check_guard_fallout`.
Всё расписано по SDLPoP прямо в задаче, включая готовые константы.
Коммиты за день, по порядку: Опора: механизм «спецсобытие порождает персонажа в слоте соперника» уже
отработан на скелете (`pop_check_skel`), а сама тень как `charid_1_shadow`
заведена на уровне 4.
## 2. Состояние репозитория
Всё закоммичено, `main` чист. `tests-host`: 5 наборов проходят
(`[char] 65`, `[geom] 3144`, `[grab] 53`, `[phys] 1723`, `[wall] 1`).
`make size-check`: OK (65 программ, роста нет).
Коммиты 2026-08-11:
| хеш | что | | хеш | что |
|---|---| |---|---|
| `8cac51d` | убраны три последних `/63 %4` в `pop_room.c` | | `a823e7e` | libbgi: блочные AND/OR/XOR акселератора (строками и колонками) |
| `72797e1` | инвентаризация ВСЕХ делений по `.asm`, три убраны | | `860468f` | libbgi: NOT_PUT тоже через акселератор + `docs/shadow_render.md` |
| `8175121` | `scr_x` таблицей на весь диапазон, включая отрицательные | | `c0075c7` | NEXT_SESSION: op-блиты сделаны, вид тени отложен |
| `fc0ede9` | байтовая таблица `x/7` вместо словарной (−1152 Б, на такт быстрее) |
| `e261a35` | замер в MAME: A/B со сборкой до правок, делений в горячем пути 0 |
| `67a4c71` | `BUG-TORCH-CHOMP-2`: застывший чомпер накрывался пламенем |
| `1b2111f` | L4-MIRROR шаги 1-2: зеркало в атласе + постановка тайла |
| `844fa6d` | L4-MIRROR шаг 4: прыжок сквозь зеркало и рождение тени |
| `8f0362f` | L4-MIRROR шаги 3 и 5 + **левый клип колонок в libbgi** |
| `3913f1e` | fore-проход поверх отражения (ноги/голова вылезали из арки) |
| `8beb66a` | заведён `FORE-DUP` с разбором |
## 2. Состояние окружения Накануне (2026-08-10) закрыт `L4-MIRROR`: шаги 1-5 + фикс fore-прохода над
отражением; протокол — [`TASKS_CLOSED.md`](TASKS_CLOSED.md#l4-mirror).
- **MAME запущена** с образом уровня 4 (`FIRST_LEVEL=4`, `PROF_BORDER=1`). ## 3. Состояние окружения
Пересобрать образ: `make PROF_FLAGS="-DPROF_BORDER=1 -DFIRST_LEVEL=4" hdd`,
после этого MAME **обязан** полный рестарт (memory
`mame_hdd_rebuild_restart`). Загрузка: `keyseq d:{ENTER}`, потом
`keyseq roomtest{ENTER}`, ждать ~18 с.
- **SDLPoP пересобран с отладочной информацией** (`-O0 -g3`). pkg-config на
машине НЕТ, собирать так:
```
cd applications/PoP/SDLPoP/src
SDLC="-I/opt/homebrew/include -I/opt/homebrew/include/SDL2 -D_THREAD_SAFE"
SDLL="-L/opt/homebrew/lib -lSDL2main -lSDL2 -Wl,-framework,Cocoa \
-L/opt/homebrew/Cellar/sdl2_image/2.8.12_1/lib -lSDL2_image"
make -j8 CFLAGS="-std=gnu99 -D_DARWIN_C_SOURCE -O0 -g3 $SDLC" LIBS="$SDLL"
```
`-std=c99` НЕ работает (прячет `strncasecmp` на Darwin), нужен `gnu99`.
- **НЕ ПРИБРАНО:** в `SDLPoP/src/seg008.c` мой диагностический `fprintf`
с меткой `DBGMIRROR` в начале `add_objtable`. Убрать перед следующей
сборкой SDLPoP (или оставить — он гейтится по `obj_type == 1 || == 4`).
## 3. XOR/OR-блит через акселератор — СДЕЛАНО 2026-08-11, тень ОТЛОЖЕНА - **MAME запущена с мостом**, но на её HDD сейчас лежат ТЕСТЫ libbgi
(`accop.exe`, `bgiimg.exe`), а не roomtest. Под roomtest пересобрать
образ: `make hdd` (или `make PROF_FLAGS="-DFIRST_LEVEL=5" hdd` — при смене
`FIRST_LEVEL` нужен `touch roomtest.c`, иначе не переедет), после чего
MAME **обязана** пройти полный рестарт (memory `mame_hdd_rebuild_restart`).
- **Остановка MAME — `mame/v306/bridge_cmd.sh 'cmd exit'`**, НЕ `kill`
(правило пользователя). `pkill -f` бывает не срабатывает, и остаются ДВА
инстанса, оба отвечающих на файловый IPC — тогда скриншоты приходят от
чужой машины. Проверять `pgrep -f mame.arm` после остановки.
- Прогон без MCP-инструментов: `bridge_cmd.sh 'cont'` → sleep ~45 с (бут
DSS) → `bridge_cmd.sh 'keyseq d:{ENTER}'``keyseq <имя>{ENTER}`
`snap /tmp/mame_snap/x.png`. **Имя .exe на образе — не длиннее 8 символов
(FAT 8.3) и БЕЗ подчёркивания** (`keyseq` не умеет `_`).
- **SDLPoP собран с отладочной информацией** (`-O0 -g3`); pkg-config на
машине нет, команда сборки — в истории этого файла (`git log -p`).
- **НЕ ПРИБРАНО:** в `SDLPoP/src/seg008.c` диагностический `fprintf` с меткой
`DBGMIRROR` в начале `add_objtable` (гейтится по `obj_type == 1 || == 4`).
**Библиотечная часть закрыта.** В libbgi поднят полный набор блочных ## 4. Что сделано в libbgi и чем это полезно дальше
операций акселератора — AND/OR/XOR/NOT, строками и колонками:
`gfx_blit_op` / `gfx_blit_part_op`, `gfx_blit_cols_op` /
`gfx_blit_cols_part_wx_op` (клип-окно и флип — как у копирующих близнецов);
опкод операции патчится SMC, одна функция на все операции. `putimage`
лишился попиксельного пути целиком (закрыт пункт 2d-1 `docs/TODO.md`).
Регресс — `tests/accop`, 10/10 PASS в MAME, побайтно; `tests/bgi_img`
получил две байтовые самопроверки. Механика и три ловушки — memory
`accel_block_ops`.
**Вид тени (два блиттера OR+XOR) отложен решением пользователя** до того, Блочные операции акселератора — полный набор AND/OR/XOR/NOT, строками и
как будут сделаны все уровни: пока тень рисуется обычной копией из атласов колонками: `gfx_blit_op` / `gfx_blit_part_op`, `gfx_blit_cols_op` /
Кида. Причина не в примитивах — XOR несовместим с нашей прозрачностью `gfx_blit_cols_part_wx_op` (клип-окно и флип — как у копирующих близнецов),
`#FF`, а операция читает ОЗУ-копию экрана, из-за чего два прохода опкод патчится SMC. `putimage` лишился попиксельного пути целиком (закрыт
оригинала вырождаются в один XOR. Всё выясненное, замеры и четыре пункт 2d-1 `docs/TODO.md`). Регресс — `tests/accop` (10/10 PASS в MAME,
варианта — [`../docs/shadow_render.md`](../docs/shadow_render.md). Ключ к побайтно). Механика и три ловушки — memory `accel_block_ops`; главные:
выбору варианта — список кадров, которыми тень реально пользуется операция читает ОЗУ-КОПИЮ экрана (не VRAM), а прозрачность `#FF` совместима
(ожидание: бег, длинный прыжок из зеркала, питьё зелья, боёвка). с AND/OR, но не с XOR.
Ниже — исходная постановка задачи, оставлена как справка. **Вид тени (OR+XOR) отложен** до готовности всех уровней — разбор, замеры и
варианты в [`../docs/shadow_render.md`](../docs/shadow_render.md).
**Что установлено (замером, не гипотезой).** Тень уровня 4 в оригинале ## 5. Остальное открытое
рисуется ДВУМЯ блитами одного и того же спрайта Кида (seg008:1602):
```c - **[FORE-DUP](BUGS_OPEN.md#fore-dup)** — передний слой тайла рисуется дважды
case 1: // shadow при перекрытии объектов (Кид+отражение всегда, Кид+соперник — весь ближний
add_midtable(obj_chtab, obj_id + 1, obj_xh, obj_xl, obj_y, blitters_2_or, 1); бой). Картинку не портит, тратит такты. **Сначала замерить, потом
add_midtable(obj_chtab, obj_id + 1, obj_xh, obj_xl + 1, obj_y, blitters_3_xor, 1); чинить** — окно клипа в своё время дало 3.2×.
``` - **[MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale)** — зеркало, поставленное
пока игрок В комнате 4, не попадает в снимок `room_fg` и потому невидимо
OR на месте, XOR со сдвигом на пиксель вправо — XOR гасит совпавшее, остаются для коллизии. В обычном прохождении недостижимо; фикс на несколько строк,
края, отсюда «контурный» вид. Это ЗАМЫСЕЛ оригинала, не артефакт SDLPoP. делать вместе со следующей правкой `pop_trob.c`.
- **[DIED-ON-BUTTON](BUGS_OPEN.md#died-on-button)** — `died_on_button`
Подтверждено печатью из живого SDLPoP (`DBGMIRROR`): и тень, и отражение идут (seg007:776) не портирован.
из `chtab=2` (собственные спрайты Кида), `swordbits=0`, обычными кадрами: - **TORCH-ANIM-RIGHT** — под запечённым пламенем могут застыть пики/меч/зелье
справа от факела; на уровнях 1-4 такого соседства нет.
``` - **[GUARD-PHYS](TASKS_OPEN.md#guard-phys)** — остаток: ветка тени в
type=4 chtab=2 img=40 dir=0 clipL=137 clipT=3 charid=0 frame=41 <- отражение `check_guard_fallout` (уходит в L5-SHADOW) и живая проверка стража,
type=1 chtab=2 img=41 dir=0 clipL=137 clipT=3 charid=1 frame=42 <- тень нажимающего напольную кнопку.
```
Различие между ними — ТОЛЬКО блиттер. Наша тень сейчас рисуется обычной
прозрачной копией, то есть выглядит вторым Кидом.
**Механизм на Sprinter** (`docs/part2/accelerator_doc.txt`, memory
`sprinter_accelerator` дополнена сегодня). Акселератор умеет блочные
AND/OR/XOR; операцию задаёт ОПКОД CPU между триггерами:
```asm
LD A,(DE) ; триггер чтения: блок из спрайта -> память акселератора
XOR (HL) ; триггер операции: блок XOR с тем, что по адресу приёмника
LD (HL),A ; триггер записи: результат обратно
```
Цена — «число байт / 7 МГц», попиксельного цикла CPU НЕТ. **Операция
ортогональна направлению**: горизонтальный/вертикальный режим (`LD L,L` /
`LD A,A`) выбирается отдельно и на операцию не влияет — то есть с нашими
column-major спрайтами ([[accel_vertical_copy]]) это работает так же, как
копия. Мнемоника: `XOR (HL)` даёт `A = A ^ (HL)`; «xor (hl),a» на Z80 нет.
### План
1. **Эксперимент в MAME на маленьком тесте в `tests/`, НЕ сразу в PoP.**
Примитив трогает ассемблерное ядро libbgi, проверять его надо в изоляции.
Цель: убедиться, что связка read-триггер / `XOR` / запись даёт ожидаемый
блок в вертикальном режиме.
2. **libbgi: `_bgi_blit_cols_op_raw`** — клон `_bgi_blit_cols_raw` (asm), где
write-триггер `LD (DE),A` заменён парой «`XOR (dst)` + `LD (dst),A`».
Наружу — `gfx_blit_cols_part_op(...)` с параметром операции
(COPY / OR / XOR), чтобы одним примитивом закрыть оба блиттера тени.
Побочно закрывается давний пункт `2d-1` из `docs/TODO.md`
(`putimage` с `XOR/OR/AND_PUT` до сих пор на попиксельном пути).
После правки libbgi — `make size-check` ОБЯЗАТЕЛЕН.
3. **PoP:** тень двумя блитами, OR на месте + XOR со сдвигом `+1` по X.
Место — ветка слота соперника в `pop_char_draw` (`pop_cdraw.c`), где уже
стоит выбор атласа и клип тени по `CHARID_1_SHADOW`.
4. **Смотреть на палитру глазами.** Тут предсказать нельзя: акселератор
XOR-ит ИНДЕКСЫ, а SDLPoP делает XOR в 24-битном RGB (`blit_xor`,
seg009:3190). DOS-оригинал (режим 13h) тоже XOR-ил индексы, то есть мы
будем БЛИЖЕ к DOS, чем SDLPoP, но конкретные цвета контура определит
раскладка нашей палитры (атласы перепакованы `pop_pack_kid.py`).
Может выйти и лучше, и мусорнее — это надо увидеть.
## 4. Остальное открытое
- **`FORE-DUP`** (`bug_list.md`) — передний слой тайла рисуется дважды при
перекрытии объектов (Кид+отражение всегда, Кид+соперник — весь ближний
бой). Картинку не портит, тратит такты. В оригинале невозможно: там
`redraw_at_char` только ПОМЕЧАЕТ тайлы. **Сначала замерить, потом чинить**
— окно клипа вместо перебора тайлов в своё время дало 3.2×.
- **`TORCH-ANIM-RIGHT`** (`bug_list.md`) — под запечённым пламенем могут
застыть не только челюсти чомпера, но и пики/меч/зелье справа от факела.
На уровнях 1-4 такого соседства нет.
- **`DIED-ON-BUTTON`** — не портирован `died_on_button` (seg007:776).
- Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1` (оба низкий приоритет), - Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1` (оба низкий приоритет),
`L3-PASS`, `L3-COLOR`, `L1-SPEED`, `TUNE-1`. `L3-COLOR`, `L1-SPEED` (игра на ~39 % быстрее оригинала), `TUNE-1`.
- **Не проверено в MAME** из вчерашнего: отражение с fore-проходом и клип
тени слева (собрано и залито, но живьём не смотрели).
## 5. Грабли, собранные сегодня ## 6. Грабли, собранные 2026-08-10/11
- **Не оценивать железо по своей же memory-заметке.** Я заявил, что accel - **Не оценивать железо по своей же memory-заметке.** Заявил, что accel
умеет только копирование и XOR потребует ~25 % кадра на CPU — неверно, умеет только копирование и XOR потребует ~25 % кадра на CPU — неверно,
поправил пользователь. Заметка описывала копирование, я принял её поправил пользователь. Заметка описывала копирование, я принял её
неполноту за свойство железа. неполноту за свойство железа. Проверять по драйверу MAME
(`mame/sources/MAME/src/mame/sinclair/sprinter.cpp`) и `docs/new/06-accel.md`.
- **lldb через FIFO — плохая идея.** Повторяющиеся `-o` при - **lldb через FIFO — плохая идея.** Повторяющиеся `-o` при
`breakpoint command add` записываются НЕПОЛНЫМИ (берётся последний), а `breakpoint command add` записываются НЕПОЛНЫМИ. **Работает надёжно:**
оставшийся от неудачной попытки `script print(... lldb.frame ...)` уронил `fprintf(stderr, ...)` прямо в SDLPoP, пересборка (секунды) и чтение stdout.
lldb прямо в обработчике точки останова. Три прыжка пользователя ушли
впустую. **Работает надёжно:** добавить `fprintf(stderr, ...)` прямо в
SDLPoP, пересобрать (он собирается за секунды) и читать stdout. В дереве
уже есть такие метки (`DBG kidobj`).
- **`make` без `hdd` не обновляет образ MAME**, а `FIRST_LEVEL` живёт в - **`make` без `hdd` не обновляет образ MAME**, а `FIRST_LEVEL` живёт в
`roomtest.c` — при смене нужен `touch roomtest.c`. Я дважды сказал `roomtest.c` — при смене нужен `touch roomtest.c`.
«образ пересобран», когда он не был. - **Доска отставала от кода на три задачи** (`L3-CHOMP`, `L3-SKEL`, шаги 3/5
- Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`: `dx` кадров `L4-MIRROR` значились несделанными). Перед планированием — сверять
Кида −5..+10, стража −2..+10, плюс `render_dx ∈ {140,0,+140}`). `TASKS_OPEN.md` с кодом грепом, а не верить записи.
Пригодится всякий раз, когда нужна таблица по экранной X. - Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`). Пригодится
всякий раз, когда нужна таблица по экранной X.
+2 -2
View File
@@ -52,6 +52,6 @@ HP и зелья, чекпойнт уровня 3.
**Что берём в работу сейчас — [`TASKS_OPEN.md`](TASKS_OPEN.md)** (сделанное с **Что берём в работу сейчас — [`TASKS_OPEN.md`](TASKS_OPEN.md)** (сделанное с
протоколами замеров — [`TASKS_CLOSED.md`](TASKS_CLOSED.md)); открытые баги — протоколами замеров — [`TASKS_CLOSED.md`](TASKS_CLOSED.md)); открытые баги —
[`bug_list.md`](bug_list.md), закрытые с разбором корней — [`BUGS_OPEN.md`](BUGS_OPEN.md), закрытые с разбором корней —
[`bug_closed.md`](bug_closed.md); план следующих уровней — [`BUGS_CLOSED.md`](BUGS_CLOSED.md); план следующих уровней —
[`../docs/levels_plan.md`](../docs/levels_plan.md). [`../docs/levels_plan.md`](../docs/levels_plan.md).
+163 -22
View File
@@ -1,4 +1,4 @@
# roomtest — ЗАКРЫТЫЕ задачи (архив досок, обновлено 2026-08-08) # roomtest — ЗАКРЫТЫЕ задачи (архив досок, обновлено 2026-08-11)
Сделанное — с протоколами замеров, граблями и причинами решений. Файл Сделанное — с протоколами замеров, граблями и причинами решений. Файл
существует не ради истории: половина записей ниже — это ЧИСЛА (сколько тактов существует не ради истории: половина записей ниже — это ЧИСЛА (сколько тактов
@@ -6,13 +6,27 @@
перечень того, что делать НЕЛЬЗЯ, потому что уже пробовали. перечень того, что делать НЕЛЬЗЯ, потому что уже пробовали.
Открытые задачи — [`TASKS_OPEN.md`](TASKS_OPEN.md); открытые баги — Открытые задачи — [`TASKS_OPEN.md`](TASKS_OPEN.md); открытые баги —
[`bug_list.md`](bug_list.md), закрытые с разбором корней — [`BUGS_OPEN.md`](BUGS_OPEN.md), закрытые с разбором корней —
[`bug_closed.md`](bug_closed.md). [`BUGS_CLOSED.md`](BUGS_CLOSED.md).
--- ---
## Приёмка уровней ## Приёмка уровней
<a id="pass-policy"></a>
### ПОЛИТИКА ПРИЁМОК — решение пользователя 2026-08-11
**Уровни 1-4: smoke-тесты пройдены.** Полные прогоны (обход всех комнат
каждого уровня) делаются **по готовности ВСЕХ уровней**, а не по одному за
этапом — отдельных задач `L3-PASS`/`L4-PASS` больше нет.
Основание: сквозные обходы дорогие, а половина находок на неполном наборе
уровней всё равно оказывается «механики ещё нет». Smoke (пройти уровень от
старта до двери) остаётся обязательным на каждом новом уровне — он снимает
блокеры, а не косметику.
Уже сделанные полные обходы уровней 1 и 2 (ниже) остаются регресс-базой.
<a id="l1-pass"></a> <a id="l1-pass"></a>
### L1-PASS. Сквозное прохождение уровня 1 — **ЗАКРЫТ 2026-08-07** ### L1-PASS. Сквозное прохождение уровня 1 — **ЗАКРЫТ 2026-08-07**
@@ -23,30 +37,30 @@
> регресс-базой для уровня 2. > регресс-базой для уровня 2.
> >
> **Полный обход комнат 2026-08-07 (пользователь): крупных багов нет.** Этим > **Полный обход комнат 2026-08-07 (пользователь): крупных багов нет.** Этим
> же прогоном закрыта [таблица обхода 24 комнат](bug_closed.md#обход-всех-24-комнат-уровня-1) > же прогоном закрыта [таблица обхода 24 комнат](BUGS_CLOSED.md#обход-всех-24-комнат-уровня-1)
> и [чек-листы ручной перепроверки фиксов](bug_closed.md#ручная-перепроверка-2026-08-03) > и [чек-листы ручной перепроверки фиксов](BUGS_CLOSED.md#ручная-перепроверка-2026-08-03)
> — обе уехали в `bug_closed.md`. > — обе уехали в `BUGS_CLOSED.md`.
Приёмка этапа 1 и одновременно регресс-база для уровня 2: от старта до двери Приёмка этапа 1 и одновременно регресс-база для уровня 2: от старта до двери
уровня одним заходом — подбор меча, страж, кнопки/ворота, пики, loose-полы, уровня одним заходом — подбор меча, страж, кнопки/ворота, пики, loose-полы,
зелье, падения. Точки, где смотрели внимательно, — закрытая косметика зелье, падения. Точки, где смотрели внимательно, — закрытая косметика
окклюзии (потолок при прыжке вверх, шов при анимации решётки, грани дальней окклюзии (потолок при прыжке вверх, шов при анимации решётки, грани дальней
колонны) и подъём на тайл-кнопку (оговорка к BUG-3 в `bug_closed.md`). колонны) и подъём на тайл-кнопку (оговорка к BUG-3 в `BUGS_CLOSED.md`).
<a id="l2-pass"></a> <a id="l2-pass"></a>
### L2-PASS. Приёмка уровня 2 — **ЗАКРЫТ 2026-08-07** ### L2-PASS. Приёмка уровня 2 — **ЗАКРЫТ 2026-08-07**
> **Smoke 2026-08-05 (пользователь): уровень 2 пройден.** Из smoke пришли > **Smoke 2026-08-05 (пользователь): уровень 2 пройден.** Из smoke пришли
> BUG-LOOSE-3 и BUG-GUARD-DEAF-1 — оба закрыты (`bug_closed.md`). Следом > BUG-LOOSE-3 и BUG-GUARD-DEAF-1 — оба закрыты (`BUGS_CLOSED.md`). Следом
> прогнан smoke уровня 3. > прогнан smoke уровня 3.
> >
> **Полный обход комнат 2026-08-07 (пользователь): крупных багов нет.** > **Полный обход комнат 2026-08-07 (пользователь): крупных багов нет.**
> Открытыми с этого прогона остались три записи в > Открытыми с этого прогона остались три записи в
> [`bug_list.md`](bug_list.md): [BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1) > [`BUGS_OPEN.md`](BUGS_OPEN.md): [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1)
> (страж всегда одного цвета), BUG-GUARD-SPLASH-1 (нет брызг при попадании по > (страж всегда одного цвета), BUG-GUARD-SPLASH-1 (нет брызг при попадании по
> стражу — **закрыт 2026-08-07**, разбор в > стражу — **закрыт 2026-08-07**, разбор в
> [`bug_closed.md`](bug_closed.md#bug-guard-splash-1)) и > [`BUGS_CLOSED.md`](BUGS_CLOSED.md#bug-guard-splash-1)) и
> [BUG-CHEAT-FIGHT-1](bug_list.md#bug-cheat-fight-1) (наш чит `+`/`` в бою > [BUG-CHEAT-FIGHT-1](BUGS_CLOSED.md#bug-cheat-fight-1) (наш чит `+`/`` в бою
> отнимает управление). Ни одна играть не мешает. > отнимает управление). Ни одна играть не мешает.
Ниже — **карта содержимого уровня, снятая прямо с `res2002.bin`**. Она Ниже — **карта содержимого уровня, снятая прямо с `res2002.bin`**. Она
@@ -56,7 +70,7 @@
**Стражи — 5, в комнатах 4, 7, 11, 15, 24** (skill 1/2/1/1/3, цвета **Стражи — 5, в комнатах 4, 7, 11, 15, 24** (skill 1/2/1/1/3, цвета
1/3/1/1/6 — цвет мы пока игнорируем, атлас один, см. 1/3/1/1/6 — цвет мы пока игнорируем, атлас один, см.
[BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1)). Заметить: страж [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1)). Заметить: страж
комнаты 24 со skill 3 — первый по-настоящему опасный. комнаты 24 со skill 3 — первый по-настоящему опасный.
**Кнопки и что они открывают** (декодировано из LINKLOC/LINKMAP): **Кнопки и что они открывают** (декодировано из LINKLOC/LINKMAP):
@@ -100,8 +114,135 @@
--- ---
### <a id="rooms-graph"></a>Справка: связность комнат уровней 1–3 (снято 2026-08-05)
Обход графа `roomlinks` (@1952, по 4 байта на комнату: L, R, U, D; 0 = нет
соседа) от стартовой комнаты — тем же методом, которым на уровне 1 нашлись
13/18/24 (см. «НЕ БАГИ» в [`BUGS_CLOSED.md`](BUGS_CLOSED.md)). Скрипт разовый,
в репозиторий не клался: чтение трёх массивов, BFS и проверка симметрии.
| уровень | старт | недостижимы | признак |
|---------|-------|-------------|---------|
| 1 | к.1 (0,0) | **13, 18, 24** | ссылки наружу есть, обратных нет |
| 2 | к.5 (1,3) | **нет** | граф полностью симметричен, все 24 достижимы |
| 3 | к.9 (2,4) | **23, 24** | то же, что на 1: односторонние ссылки, обе комнаты **полностью пустые** |
```
ур.3: 23 L→4, у 4 R=22 | 24 L→2, у 2 R=7
23 R→22, у 22 L=4 | 24 R→7, у 7 L=2
| 24 U→16, у 16 D=0
на 23 ссылается только 24, на 24 — только 23; тайлы обеих = все empty
```
То есть на уровне 3 это даже более чистый случай, чем на уровне 1: там в
брошенных комнатах была геометрия, здесь — пустота. Практический вывод тот
же: **в 23/24 возможен «мусор в шве»** (наш рендер кромки читает крайнюю
колонку соседа ПО ССЫЛКЕ, а сосед соседом себя не считает), приоритет багов
там низкий, в игре их не видно.
**Уровень 2 — недостижимых нет, но есть три КОЛОДЦА без выхода** (единственная
связь — вверх, откуда Кид падает):
```
к.10 U→4 пики(2,2) + пол — падение из комнаты 4
к.14 U→21 шахта 2 тайла шириной, дно = обломки
к.17 U→15 то же
```
Это не баги данных: в 14/17 попадают только падением насмерть, а из 10
(если выжил) выхода нет вовсе — так в оригинале. При приёмке не считать
«застрял» багом.
---
## Уровни и механика ## Уровни и механика
<a id="l4-mirror"></a>
### L4-MIRROR. Зеркало уровня 4 и тень — **ЗАКРЫТА 2026-08-11**
Пять шагов из шести сделаны и **проверены пользователем в MAME 2026-08-11**;
шестой (вид тени) осознанно отложен.
| шаг | что | коммит |
|---|---|---|
| 1 | зеркало в атласе: `tile_table[0x0D]` база 75 / фронт 77, `MIRROR_ENV_IDS` + `FORE_ENV_IDS` в `pop_pack_bg.py` | `1b2111f` |
| 2 | постановка тайла: `place_mirror` в `pop_trob.c` по переходу `pop_leveldoor_open` 0/2 → 1 (`animate_leveldoor`, seg007:0457) | `1b2111f` |
| 3 | отражение (`check_mirror`, seg003:0798) — отдельной функцией `pop_mirror_draw`, со своим heal | `8f0362f`, `3913f1e` |
| 4 | прыжок сквозь зеркало и рождение тени (seg004:0239 + seg003:0798..08A9 + seg002:081D/1131) | `844fa6d` |
| 5 | клип тени слева `obj_clip_left = 137 + (mirror_column4)*32` (seg008:1699) + новый примитив `gfx_blit_cols_part_wx` в libbgi | `8f0362f` |
**Почему зеркало пришлось добавлять в атлас явно:** тайла 13 нет ни в одном
уровне статически — проверено перебором всех 15 `res200N.bin`, ноль
попаданий, поэтому `render_room.py` его не видит и на месте зеркала был бы
чёрный провал (memory `pop_atlas_dynamic_ids`).
**Почему отражение — отдельная функция, а не третий слот `Char`:** это
структура самого оригинала — отражение идёт сокращённым путём
`load_frame_to_obj` + `add_objtable(4)`, без клинка, брызг, пропуска кадра;
гейтить всё это в общем теле `pop_cdraw` значило бы добавить ветки в самый
горячий путь. Побочный эффект — [FORE-DUP](BUGS_OPEN.md#fore-dup): передний
слой тайла рисуется дважды, когда футпринты Кида и отражения накрывают один
тайл (всегда, они стоят на одном тайле). Картинку не портит, тратит такты.
**Почему левый клип сделан примитивом libbgi, а не «нарисовать и вернуть фон
поверх лишнего»** (подсказка пользователя): для column-major левая обрезка
стоит ровно столько же, сколько правая — колонка это непрерывный кусок ОЗУ,
меняются стартовая колонка источника и экранная X. Внутри это уже было (так
клипается левый край экрана), наружу не было выведено.
**Отложено — вид тени** (шаг 6): в оригинале она рисуется ДВУМЯ блитами
одного спрайта, `blitters_2_or` на месте и `blitters_3_xor` со сдвигом +1 px
(seg008:1602); у нас пока обычная копия, то есть тень выглядит вторым Кидом.
Разбор, замеры и варианты — [`../docs/shadow_render.md`](../docs/shadow_render.md);
блочные операции акселератора под это в libbgi уже есть (`tests/accop`).
**Открытый краевой случай** — [MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale):
`place_mirror` пишет тайл в данные уровня, но не в снимок комнаты `room_fg`,
по которому работает коллизия.
<a id="l3-chomp"></a>
### L3-CHOMP. Чомперы — **СДЕЛАНЫ 2026-08-08**
Коммиты `dc0bd47` (анимация, отрисовка, смерть в челюстях), `4d4323f`
(передние зубья через `pop_fore_b` + ветка в `draw_tile`), `db4106a`
(перед чомпером Кид разбегается сразу, без осторожного шага — `safe_step` по
оригиналу), `1461ed5` (фикс регрессии, см. ниже).
Портировано: `animate_chomper` (seg007:0448), `next_chomper_timing`
(seg007:0F9A — 15,12,9,6,13,10,7,14,11,8 по кругу), `start_anim_chomper`
(seg007:08C7), `start_chompers` (seg007:0F13) — все в `pop_trob.c`,
состояние в `room_modif`, как у пик и ворот. Смерть: `SEQ_54_CHOMPED` /
`FRAME_178_CHOMPED` + `check_chomped_guard` для соперника (`pop_map.c`).
Отрисовка — `pop_chomp_pose` (`pop_bg.c`) и холодная перерисовка тайла в
`pop_room.c`. Ассеты упакованы явным набором кадров (`CHOMPER_BOT_IDS`
101-105, `TOP` 111-113, `FORE` 106-110 + кровь 114-123 mono-силуэтом).
**Грабли, стоившие регрессии (`1461ed5`):** `start_chompers` вызывается на
смене ряда персонажа, а у нас `seqtbl` читается ЧЕРЕЗ ОКНО W0 — прямой вызов
из `play_seq` переключал окно посреди чтения байткода. Вызов отложен через
флаг `chomp_pending` (`pop_kid.c`) и делается ПОСЛЕ цикла интерпретатора.
Хвосты в багах: [BUG-CHOMP-JUMP-1](BUGS_OPEN.md#bug-chomp-jump-1) (низкий,
маловоспроизводим) и закрытые BUG-TORCH-CHOMP-1/2 (пламя факела и застывший
чомпер — [`BUGS_CLOSED.md`](BUGS_CLOSED.md)).
<a id="l3-skel"></a>
### L3-SKEL. Скелет уровня 3 — **СДЕЛАН 2026-08-07, принят smoke-прогоном**
В данных уровня 3 стражей нет вообще (`guards_tile` пуст во всех 24
комнатах) — единственный враг это скелет, и он не «страж из данных», а
**спецсобытие** `check_skel` (seg002:1044): в комнате 1, когда
`Kid.curr_col` == 2 или 3 и дверь уровня открыта, тайл `tiles_21_skeleton`
(комната 1, тайлпос 15) стирается в пол, а на его месте поднимается
персонаж — `charid_4_skeleton`, меч сразу вынут, `seq_88_skel_wake_up`,
skill 2, HP 3. Ещё два тайла скелета (комнаты 17 и 19) — декорация, они не
оживают. Атлас — `pop_pack_guard.py SKEL``poc/res/skel/g0..g3.atl`.
Проверено в MAME: бой, падение в пропасть, окклюзия (2026-08-07); принят
smoke-прогоном уровней 1-4 (2026-08-11). **Этот же механизм**
«спецсобытие порождает персонажа в слоте соперника» — база для тени уровня 5
([L5-SHADOW](TASKS_OPEN.md#l5-shadow)).
<a id="l3-chkp"></a> <a id="l3-chkp"></a>
### L3-CHKP. Чекпойнт уровня 3 — **СДЕЛАНО 2026-08-06** (коммит 0cd6b2d) ### L3-CHKP. Чекпойнт уровня 3 — **СДЕЛАНО 2026-08-06** (коммит 0cd6b2d)
@@ -119,7 +260,7 @@ SDLPoP: смерть В комнате 7 вернула его в стартов
**Проверено в MAME:** вход в 7 флаг не ставит, уход влево — ставит; респавн в **Проверено в MAME:** вход в 7 флаг не ставит, уход влево — ставит; респавн в
комнате 2; после обычной смерти (без чекпойнта) плиты восстанавливаются как комнате 2; после обычной смерти (без чекпойнта) плиты восстанавливаются как
раньше. Вживую в игре (не читом) — потрогать на приёмке уровня 3 раньше. Вживую в игре (не читом) — потрогать на приёмке уровня 3
([L3-PASS](TASKS_OPEN.md#l3-pass)). ([L3-PASS](TASKS_CLOSED.md#pass-policy)).
### L2. Переход на уровень 2 и его игра — **МАШИНЕРИЯ СДЕЛАНА 2026-08-04** ### L2. Переход на уровень 2 и его игра — **МАШИНЕРИЯ СДЕЛАНА 2026-08-04**
@@ -163,7 +304,7 @@ Shift+L → уровень 3 (комната 9). То есть цепочка
Контент уровня 2 (сквозное прохождение, выход через дверь) закрыт отдельно — Контент уровня 2 (сквозное прохождение, выход через дверь) закрыт отдельно —
[L2-PASS](#l2-pass). Цвет стража из данных остался открытым багом — [L2-PASS](#l2-pass). Цвет стража из данных остался открытым багом —
[BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1). [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1).
### L1-START. Старт по данным уровня — **СДЕЛАНО 2026-08-01** ### L1-START. Старт по данным уровня — **СДЕЛАНО 2026-08-01**
@@ -203,30 +344,30 @@ Shift+L → уровень 3 (комната 9). То есть цепочка
✅ Косметика тоже закрыта: BUG-DOOR-CLIP (обрезка силуэта правым косяком ✅ Косметика тоже закрыта: BUG-DOOR-CLIP (обрезка силуэта правым косяком
проёма) — недоставало второй половины `clip_char` (`obj_clip_right`) и проёма) — недоставало второй половины `clip_char` (`obj_clip_right`) и
обрезки СПРАВА у колоночного блита. В libbgi добавлен обрезки СПРАВА у колоночного блита. В libbgi добавлен
`gfx_blit_cols_part_w(..., maxw)`; разбор — в [`bug_closed.md`](bug_closed.md). `gfx_blit_cols_part_w(..., maxw)`; разбор — в [`BUGS_CLOSED.md`](BUGS_CLOSED.md).
### L1-TRIAGE. Ревизия багов — **ЗАКРЫТА** (часть 1 — 2026-08-01, хвост — 2026-08-07) ### L1-TRIAGE. Ревизия багов — **ЗАКРЫТА** (часть 1 — 2026-08-01, хвост — 2026-08-07)
**Три Critical'а прогнаны в MAME и закрыты** (протокол с числами — в **Три Critical'а прогнаны в MAME и закрыты** (протокол с числами — в
[`bug_closed.md`](bug_closed.md), раздел «Проверено в MAME 2026-08-01»): [`BUGS_CLOSED.md`](BUGS_CLOSED.md), раздел «Проверено в MAME 2026-08-01»):
BUG-1 (провал на row 1 при переходе через открытые ворота) и BUG-2 BUG-1 (провал на row 1 при переходе через открытые ворота) и BUG-2
(ping-pong при возврате) **не воспроизводятся**, BUG-3 (окклюзия climb-up на (ping-pong при возврате) **не воспроизводятся**, BUG-3 (окклюзия climb-up на
кнопке) закрыт фиксом `tile_code_drawn` от 2026-07-28. Заодно снят неверный кнопке) закрыт фиксом `tile_code_drawn` от 2026-07-28. Заодно снят неверный
диагноз BUG-1: репроекция Y при БОКОВОМ переходе — не наш пробел, а точное диагноз BUG-1: репроекция Y при БОКОВОМ переходе — не наш пробел, а точное
поведение `goto_other_room` (`SDLPoP/src/seg002.c:390` меняет только `x`). поведение `goto_other_room` (`SDLPoP/src/seg002.c:390` меняет только `x`).
Список разделён на [`bug_list.md`](bug_list.md) (открытое) и Список разделён на [`BUGS_OPEN.md`](BUGS_OPEN.md) (открытое) и
[`bug_closed.md`](bug_closed.md) (закрытое + разбор корней). [`BUGS_CLOSED.md`](BUGS_CLOSED.md) (закрытое + разбор корней).
**Косметика окклюзии тоже закрыта** — BUG-CEIL-1/2/3 и BUG-OCCL-1 были **Косметика окклюзии тоже закрыта** — BUG-CEIL-1/2/3 и BUG-OCCL-1 были
починены кодом ещё в июле, а записи никто не снял: `ceil_over_kid_tile` починены кодом ещё в июле, а записи никто не снял: `ceil_over_kid_tile`
(`pop_bg.c:1275`), `pop_ceil_modif` + `pop_ceil_shake_draw`/`_bake_empty` (`pop_bg.c:1275`), `pop_ceil_modif` + `pop_ceil_shake_draw`/`_bake_empty`
(`:547,815,827`), `bar` с `POP_YOFF+3` в `pop_room_redraw_seam_left` (`:793`), (`:547,815,827`), `bar` с `POP_YOFF+3` в `pop_room_redraw_seam_left` (`:793`),
разделение слоёв по `add_backtable` vs `ptr_add_table` в `overlay_mid_tile` разделение слоёв по `add_backtable` vs `ptr_add_table` в `overlay_mid_tile`
(`:1367`). Разбор — в [`bug_closed.md`](bug_closed.md). (`:1367`). Разбор — в [`BUGS_CLOSED.md`](BUGS_CLOSED.md).
**Хвост закрыт 2026-08-07:** таблица обхода 24 комнат уровня 1 — прогоном **Хвост закрыт 2026-08-07:** таблица обхода 24 комнат уровня 1 — прогоном
всех комнат уровней 1 и 2 (крупных багов нет), см. всех комнат уровней 1 и 2 (крупных багов нет), см.
[`bug_closed.md`](bug_closed.md#обход-всех-24-комнат-уровня-1). [`BUGS_CLOSED.md`](BUGS_CLOSED.md#обход-всех-24-комнат-уровня-1).
--- ---
@@ -301,7 +442,7 @@ BANK4 pop_cdraw 4 625 5 905 +1 280 (сюда переехала отрис
То есть слияние двух копий дало ~3.2 КБ, но **главный выигрыш не в банке 2, То есть слияние двух копий дало ~3.2 КБ, но **главный выигрыш не в банке 2,
а в резиденте**: отрисовка Кида уехала из W1 в банк, и куча выросла втрое а в резиденте**: отрисовка Кида уехала из W1 в банк, и куча выросла втрое
(2 023 → 6 333 Б). Банку 2 при этом досталось всего +265 Б свободного (2 023 → 6 333 Б). Банку 2 при этом досталось всего +265 Б свободного
места — **под [L3-CHOMP](TASKS_OPEN.md#l3-chomp) этого мало**, разгрузку места — **под [L3-CHOMP](TASKS_CLOSED.md#l3-chomp) этого мало**, разгрузку
`pop_bg` придётся делать отдельно (свободные номера банков — 7+). `pop_bg` придётся делать отдельно (свободные номера банков — 7+).
**Проверено:** `tests-host` — все 5 наборов зелёные, трассы физики не **Проверено:** `tests-host` — все 5 наборов зелёные, трассы физики не
@@ -615,7 +756,7 @@ make ALLOCS=100000 — как раньше: минимальный код, д
> переполняется» отпадает; сам ФИКС (плотный опрос `kbd_raw_poll`) остаётся > переполняется» отпадает; сам ФИКС (плотный опрос `kbd_raw_poll`) остаётся
> верным и нужным — переполнение вызывает не Shift, а короткая жизнь > верным и нужным — переполнение вызывает не Shift, а короткая жизнь
> импульса IRQ (пункт 3 гипотезы) плюс DI-окна графики. Разбор и следствия > импульса IRQ (пункт 3 гипотезы) плюс DI-окна графики. Разбор и следствия
> — [BUG-KBD-5](bug_closed.md). > — [BUG-KBD-5](BUGS_CLOSED.md).
> >
> **Итог (2026-08-01).** Причина — не наш код и не DI-окна графики: импульс > **Итог (2026-08-01).** Причина — не наш код и не DI-окна графики: импульс
> запроса прерывания здесь теряется примерно в 44 % случаев, и трёхбайтовый > запроса прерывания здесь теряется примерно в 44 % случаев, и трёхбайтовый
+112 -292
View File
@@ -1,30 +1,33 @@
# roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-08) # roomtest — доска ОТКРЫТЫХ задач (обновлено 2026-08-11)
Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать, Что берём в работу сейчас и в каком порядке. Каждая запись: что сделать,
почему именно сейчас, чем подтверждать результат. почему именно сейчас, чем подтверждать результат.
- закрытые задачи с протоколами и замерами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md); - закрытые задачи с протоколами и замерами — [`TASKS_CLOSED.md`](TASKS_CLOSED.md);
- открытые баги — [`bug_list.md`](bug_list.md), закрытые с разбором корней — - открытые баги — [`BUGS_OPEN.md`](BUGS_OPEN.md), закрытые с разбором корней —
[`bug_closed.md`](bug_closed.md); [`BUGS_CLOSED.md`](BUGS_CLOSED.md);
- планы фаз — `../docs/PORT_PLAN.md`, `../docs/layout_plan_v2.md`, - планы фаз — `../docs/PORT_PLAN.md`, `../docs/layout_plan_v2.md`,
`../docs/levels_plan.md`. `../docs/levels_plan.md`.
**Состояние на 2026-08-07:** пользователь прогнал ВСЕ комнаты уровней 1 и 2 — **Состояние на 2026-08-11 (сверено с кодом, не только с доской):**
крупных багов нет. Приёмки [L1-PASS](TASKS_CLOSED.md#l1-pass) и
[L2-PASS](TASKS_CLOSED.md#l2-pass) закрыты; с прогона открыты три записи по
уровню 2, из них цвет стража и брызги уже закрыты (см.
[`bug_closed.md`](bug_closed.md)); открытым остался чит `+`/`` в бою.
Уровень 3: **скелет сделан и проверен в MAME 2026-08-07** (бой, падение в
пропасть, окклюзия), чомперов ещё нет.
**Разгрузка банка 2 сделана 2026-08-08** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2)): 90.4 % → - **уровни 1-4 приняты smoke-тестами** (пользователь). Полные обходы всех
**35.8 %, свободно 10 512 Б** — холодная половина уехала в банк 7 комнат делаются по готовности ВСЕХ уровней — политика приёмок в
(`pop_room.c`), чомперам места с запасом. [`TASKS_CLOSED.md`](TASKS_CLOSED.md#pass-policy); отдельных задач
`L3-PASS`/`L4-PASS` больше нет. Уже сделанные полные обходы уровней 1 и 2
**DRAW-CHAR сделана и ПРОВЕРЕНА 2026-08-08** (протокол и замеры — остаются регресс-базой;
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char)): отрисовка теперь одна на - **[L4-MIRROR](TASKS_CLOSED.md#l4-mirror) закрыта**: зеркало, отражение,
всех `Char`. Банку 2 она дала всего +265 Б — место под чомперов дала уже прыжок сквозь него и рождение тени проверены в MAME. Хвосты — вид тени
[MEM-BANK2](TASKS_CLOSED.md#mem-bank2). (отложен, [`../docs/shadow_render.md`](../docs/shadow_render.md)) и
[MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale);
- **[L3-CHOMP](TASKS_CLOSED.md#l3-chomp) и [L3-SKEL](TASKS_CLOSED.md#l3-skel)
закрыты** (2026-08-08 / 2026-08-07);
- **тайлсет palace сделан** — `pop_bg_load(type)`, `pal_*.atl`, дворцовая
кладка `wall_pattern`, решётчатые тайлы 25-29 и в `tile_table`, и в
коллизии (`tile_is_floor` совпадает с seg006:0628). То есть шаг 2
`levels_plan.md` закрыт;
- **libbgi:** блочные AND/OR/XOR/NOT акселератора (2026-08-11, `tests/accop`)
— задел под вид тени и под любые эффекты «поверх того, что уже нарисовано».
Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга; Правило проекта в силе: механику сверять с `../SDLPoP/src/` ДО кодинга;
диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не диагноз платформы подтверждать артефактом (брейкпоинт/дамп/.asm), а не
@@ -32,41 +35,40 @@
--- ---
## ТЕКУЩАЯ ЦЕЛЬ: уровни 1–3 (подземелье) отлажены целиком ## ТЕКУЩАЯ ЦЕЛЬ: уровень 5
Решение 2026-08-04: **palace (уровни 4+) откладываем**, доводим до Уровни 1-4 играются (smoke). Дальше идём по порядку уровней; уровень 5 —
играбельности три dungeon-уровня. Основание — они не требуют ни одного следующий.
нового ассета фона: инвентарь тайлов, снятый с `res200N.bin`, показывает,
что новое появляется только так — **Хорошая новость по ассетам: уровень 5 не приносит НИ ОДНОГО нового тайла.**
Инвентарь, снятый перебором `res2005.bin` (fg & 0x1F):
``` ```
ур. 1 empty, floor, spike, pillar, gate, closer, potion, loose, debris, ур. 5: empty, floor, spike, pillar, gate, closer, doortop_with_floor(7),
opener, level_door L/R, torch, wall, skeleton, sword ← всё есть bigpillar_bottom(8), bigpillar_top(9), potion, loose, doortop(12),
ур. 2 bigpillar_bottom(8), bigpillar_top(9), doortop(12) ← есть (2026-08-04) debris, opener, level_door L/R, chomper(18), torch, wall,
ур. 3 chomper(18) ← НЕТ механики lattice_pillar(25)…lattice_right(29)
ур. 4 lattice_pillar(25)…lattice_right(29) + тайлсет palace ← отложено
``` ```
— всё это уже встречалось на уровнях 1-4 и портировано. Единственное новое
на уровне 5 — **спецсобытие «тень крадёт зелье»**.
| # | Задача | Что | Блокирует | | # | Задача | Что | Блокирует |
|---|--------|-----|-----------| |---|--------|-----|-----------|
| 1 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида (одна над `Char`) | **ядро сделано 2026-08-07**; открыт живой сценарий в MAME | | 1 | [L5-SHADOW](#l5-shadow) | **СЛЕДУЮЩАЯ**: тень уровня 5 (крадёт зелье в комнате 24) + движок автодвижений | прохождение ур. 5 |
| | [DRAW-CHAR](TASKS_CLOSED.md#draw-char) | отрисовка одна на всех `Char`**закрыта и проверена 2026-08-08** | — | | 2 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида — ядро сделано, остаток: ветка ТЕНИ в `check_guard_fallout` и живая проверка кнопки под стражем | ур. 5+ (тень) |
| — | [MEM-BANK2](TASKS_CLOSED.md#mem-bank2) | разгрузка банка 2 — **закрыта 2026-08-08**: 90.4 % → 35.8 %, свободно 10 512 Б | — | | — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3**. Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях |
| 2 | [L3-CHOMP](#l3-chomp) | **СЛЕДУЮЩАЯ**: чомперы (5 шт) | прохождение ур. 3 |
| — | [L3-SKEL](#l3-skel) | скелет ур. 3 — **сделан 2026-08-07**, ждёт финальной приёмки | — |
| 3 | [L3-PASS](#l3-pass) | приёмка уровня 3 (обход комнат) | закрытие цели |
| 4 | [L4-MIRROR](#l4-mirror) | **АКТИВНА с 2026-08-10 по решению пользователя**: зеркало уровня 4 + тень | прохождение ур. 4 |
| — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3** (игра быстрее на треть). Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях |
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент | | — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности | | — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
| — | [MEM](#mem-next) | следующий шаг разгрузки W1/W2 | берётся по факту нехватки места |
Сделанное по этой цели — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md): L2 (машинерия Сделанное — в [`TASKS_CLOSED.md`](TASKS_CLOSED.md): L4-MIRROR, L3-CHOMP,
уровней), L2-PASS, L1-PASS, [L3-CHKP](TASKS_CLOSED.md#l3-chkp) (чекпойнт L3-SKEL, L3-CHKP, L2 (машинерия уровней), L2-PASS, L1-PASS, DRAW-CHAR,
уровня 3), MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS. MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
--- ---
## Ждёт ФИНАЛЬНОЙ приёмки уровней 13 ## Ждёт ФИНАЛЬНОЙ приёмки (полные обходы по готовности всех уровней)
Сюда попадает то, что уже работает в проверочном прогоне, но должно быть Сюда попадает то, что уже работает в проверочном прогоне, но должно быть
подтверждено на сквозных прогонах уровней — потому что задевает механику подтверждено на сквозных прогонах уровней — потому что задевает механику
@@ -87,6 +89,63 @@
## P0 — делаем сейчас ## P0 — делаем сейчас
### <a id="l5-shadow"></a>L5-SHADOW. Тень уровня 5: крадёт зелье — **СЛЕДУЮЩАЯ**
Единственная новая механика уровня 5 (тайлов новых нет вовсе, см. цель выше).
Тень появляется в комнате 24, дожидается, пока откроется дверь, идёт к зелью,
**выпивает его** и уходит за левый край. Боя нет.
**Как это в оригинале** (всё сверено по коду, `custom->*` — это дефолты 1.0):
| что | где | суть |
|---|---|---|
| появление | `check_shadow`, seg002:0064 | при СМЕНЕ КОМНАТЫ: если `current_level == 5` и `drawn_room == 24`, и тайл (кол 3, ряд 0) всё ещё `tiles_10_potion` — породить тень |
| порождение | `do_init_shad`, seg002:0000 | `memcpy(&Char, init_shad_5, 7)` + `seqtbl_offset_char(2 /*stand*/)`, `charid = charid_1_shadow`, `demo_time = 0`, `guard_skill = 3`, `guardhp_* = 4`, `saveshad()` |
| данные | `init_shad_5` | `{0x0F, 0x37, 0x37, 0, 0xFF, 0, 0}` = frame 15, x 55, y 55, direction 0, curr_col 1, curr_row 0, action 0 |
| поведение | `autocontrol_shadow_level5`, seg002:1157 | в комнате 24: пока `demo_time == 0` — ждать, пока дверь (кол 1, ряд 0) не откроется (`modif >= 80`), затем `demo_index = 0`; дальше каждый кадр `do_auto_moves(shad_drink_move)`; при `Char.x < 15``clear_char()` |
| движения | `do_auto_moves`, seg002:1089 | крошечный интерпретатор: `demo_time++`, по таблице `{time, move}` выбирается запись, `move` = 0 nothing / 1 forward / 2 backward / 3 up / 4 down / 5 up+forward / 6 shift / 7 move_7; 1 = ничего, −2 = конец |
| таблица | `shad_drink_move` (data.h:866) | `{0x00,0} {0x01,1} {0x0E,0} {0x12,6} {0x1D,7} {0x2D,2} {0x31,1} {0xFF,2}` — 8 записей по 2 байта |
**Что из этого у нас уже есть:**
- **механизм «спецсобытие порождает персонажа в слоте соперника»** —
`pop_check_skel` (`guards.c`, зовётся из `roomtest.c` в тике); тень
уровня 5 садится на тот же шов, только условие другое;
- **тень как `charid_1_shadow`** — заведена под уровень 4
([L4-MIRROR](TASKS_CLOSED.md#l4-mirror)): своя ветка ИИ
(`autocontrol_shadow` + `autocontrol_shadow_level4`), выбор таблицы кадров
Кида (`pop_frame_tbl_is_guard`), отрисовка спрайтами Кида;
- **питьё зелья** — `SEQ_78_DRINK` и `get_item` в `pop_ctrl.c` (Кид уже
умеет); тень «нажимает» те же кнопки через автодвижения;
- **зелья как trob** — фаза пузырька, тип в старших битах (`pop_trob.c`).
**Что писать:**
1. `do_auto_moves` + таблица `shad_drink_move` + `demo_time`/`demo_index`
интерпретатор ~30 строк, кладётся рядом с `autocontrol_shadow` в
`guards.c` (банк 1). «Движения» — это те же переменные управления, что
заполняет `read_user_control` (`pop_ctrl.c`), так что `move_*` сводятся к
присваиваниям.
2. `do_init_shad(init_shad_5, seq stand)` — общий порождатель тени; пригодится
и на уровнях 6 и 12 (`init_shad_6`, `init_shad_12` — те же 7 байт).
3. Ветка `check_shadow` для уровня 5 — по образцу `pop_check_skel`, вызов из
того же места тика.
4. `autocontrol_shadow_level5`.
5. **Ветка ТЕНИ в `check_guard_fallout`** (seg002:0241): тень падает, только
если она в свободном полёте (`action == 4`), и тогда
`loadshad(); clear_char(); saveshad()`. Сейчас в `pop_guard_fallout`
(`pop_guard.c`) есть ветки стража и скелета, а тени нет — комментарий там
обещает её «вместе с L3-SKEL», но она относится именно к тени.
**Чем подтверждать:** smoke уровня 5 — дойти до комнаты 24, увидеть, как
тень выходит после открытия двери, выпивает зелье (тайл зелья исчезает) и
уходит влево. Сверять последовательность движений с живым SDLPoP на том же
месте — таблица `shad_drink_move` короткая, расхождение будет видно сразу.
**Оговорка по виду:** тень пока рисуется обычной копией спрайтов Кида, то
есть выглядит вторым Кидом — это [отложенный](../docs/shadow_render.md)
вопрос, к механике уровня 5 отношения не имеет.
### <a id="guard-phys"></a>GUARD-PHYS. Страж живёт по тем же правилам, что Кид — **ЯДРО СДЕЛАНО 2026-08-07** ### <a id="guard-phys"></a>GUARD-PHYS. Страж живёт по тем же правилам, что Кид — **ЯДРО СДЕЛАНО 2026-08-07**
> **Что уже работает** (решение пользователя: переносим физику на `Char`, > **Что уже работает** (решение пользователя: переносим физику на `Char`,
@@ -126,13 +185,17 @@
> 3 → 2, X перенесён через шов), ровно как в SDLPoP. Условия отбора > 3 → 2, X перенесён через шов), ровно как в SDLPoP. Условия отбора
> покрыты тестами `t_char` (7 сценариев: пороги 91/165, «не бой», мёртвый, > покрыты тестами `t_char` (7 сценариев: пороги 91/165, «не бой», мёртвый,
> вверх/вниз, занятая соседняя комната). **Сцена вскрыла отдельный баг — > вверх/вниз, занятая соседняя комната). **Сцена вскрыла отдельный баг —
> [BUG-SWORD-GHOST-1](bug_list.md#bug-sword-ghost-1): при переходе в бою Кид > [BUG-SWORD-GHOST-1](BUGS_CLOSED.md#bug-sword-ghost-1): при переходе в бою Кид
> прячет меч и дальше дерётся пустой рукой.** > прячет меч и дальше дерётся пустой рукой.**
> >
> **Осталось (потому и запись открыта):** > **Осталось (потому и запись открыта) — ревизия 2026-08-11 по коду:**
> 1. `check_chomped_guard` — вместе с [L3-CHOMP](#l3-chomp); > 1. ~~`check_chomped_guard`~~**сделан** вместе с
> 2. ветки `check_guard_fallout` для тени и скелета (скелет возрождается в > [L3-CHOMP](TASKS_CLOSED.md#l3-chomp) (`pop_map.c`);
> комнате 3) — вместе с [L3-SKEL](#l3-skel); > 2. ветки `check_guard_fallout`: **скелет сделан** (возрождается в комнате 3,
> `pop_guard_fallout` в `pop_guard.c`), **ветки ТЕНИ нет** — падает только
> в свободном полёте, `loadshad/clear_char/saveshad`; идёт в
> [L5-SHADOW](#l5-shadow) п. 5 (комментарий в коде обещает её «вместе с
> L3-SKEL» — устарел, это про тень);
> 3. страж, нажимающий напольную кнопку, вживую не проверялся (код — > 3. страж, нажимающий напольную кнопку, вживую не проверялся (код —
> общий `check_press`). > общий `check_press`).
@@ -187,90 +250,6 @@
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
живой страж — переход не происходит. живой страж — переход не происходит.
### <a id="l3-chomp"></a>L3-CHOMP. Чомперы — механика уровня 3
**Где:** 5 штук, комнаты 5(тайл 4), 16(23,24,25), 22(26). Дальше они почти
на каждом уровне, так что это вложение не только в ур. 3.
**Что портировать:** `animate_chomper` (seg007) — состояние в `room_modif`,
как у пик/ворот, значит шаблон уже отработан (`gates_spikes_plan.md`);
коллизия и смерть Кида в сомкнутых челюстях (`seg004`/`seg006`); отрисовка
кадра по модификатору (`draw_tile_anim`, ветка `tiles_18_chomper`).
**Место в банке 2 расчищено** ([MEM-BANK2](TASKS_CLOSED.md#mem-bank2), 2026-08-08):
35.8 %, **свободно 10 512 Б** (было 1074 Б) — холодная половина слоя фона
уехала в банк 7 (`pop_room.c`). Считать место ДО кодинга всё равно
обязательно (`levels_plan.md` §5.1); чомпер — это анимация тайла (холодная
перерисовка), поэтому его отрисовка ложится в `pop_room.c`, а состояние —
в `room_modif`, как у пик и ворот.
**Ассеты УЖЕ упакованы (2026-08-07):** `pop_pack_bg.py` кладёт в атлас весь
набор кадров чомпера явно (`CHOMPER_BOT_IDS` 101-105, `CHOMPER_TOP_IDS`
111-113, `CHOMPER_FORE_IDS` 106-110 + кровь 114-123 mono-силуэтом цветом 12).
Раньше в атласе не было НИ ОДНОГО его кадра: `render_room.py` пропускает
анимированные тайлы, а `tile_table[0x12].base_id = 0` — отсюда и «чомпера не
видно вовсе». Рост: `pop_env3.atl` 5200 -> 12937 Б, `pop_fore.atl` 7763 ->
12359 Б, число EMM-страниц НЕ изменилось (7).
**Чем подтверждать:** комната 22 уровня 3 — чомпер (2,6) анимируется и
рисуется (сейчас его не видно вовсе, см. раздел «Уровень 3» в
[`bug_list.md`](bug_list.md)); смерть Кида в сомкнутых челюстях.
### <a id="l3-skel"></a>L3-SKEL. Скелет — единственный противник уровня 3
**Важно:** в данных уровня 3 **стражей нет вообще** (`guards_tile` пуст во
всех 24 комнатах). Единственный враг — скелет, и он не «страж из данных»,
а **спецсобытие** `check_skel` (seg002:1044):
> в комнате 1, когда `Kid.curr_col` == 2 или 3 и дверь уровня открыта,
> тайл `tiles_21_skeleton` (комната 1, тайлпос 15) стирается в пол, а на
> его месте поднимается персонаж: `charid_4_skeleton`, меч сразу вынут,
> `seq_88_skel_wake_up`, skill 2, HP 3.
Ещё два тайла скелета (комнаты 17 и 19) — декорация, они не оживают.
> **Сделано 2026-08-07 (ждёт живой проверки на уровне 3):**
> - **атлас скелета** — `pop_pack_guard.py SKEL` собирает `poc/res/skel/g0..g3.atl`
> (28 кадров, 30 КБ, 4 EMM-страницы); упаковщик получил параметр набора
> (`GUARD`/`SKEL`). Палитра у скелета СВОЯ (`SKEL/res750.pal`), а не из
> `res10.bin`: на уровне 3 `tbl_guard_type != 0`, значит
> `curr_guard_color = 0` и оригинал палитру не подменяет вовсе;
> - `pop_guard_load` выбирает набор по типу уровня и заливает палитру
> скелета; Makefile кладёт атласы в `SKEL\` на диск;
> - **`check_skel`** (seg002:1042) — порт в `guards.c`: уровень 3, комната 1,
> дверь уровня открыта, `Kid.curr_col` 2 или 3, тайл 21 на (5,1) → тайл
> стирается в пол (обе страницы), персонаж встаёт с `seq_88_skel_wake_up`,
> мечом наголо, skill 2, HP 3. Зовётся из главного цикла ДО логики
> персонажей, как в `play_frame`;
> - **`leveldoor_open`** — флаг появился (`pop_state.c`), взводит анимация
> двери при `modif >= 43` (seg007:456);
> - **`enter_guard`** — ветка `charid_4_skeleton`: встаёт сразу активным
> (меч вынут), а не в стойке покоя;
> - **возрождение** — `check_guard_fallout`: упавший скелет, под комнатой
> которого лежит комната 3, появляется там снова (x 133, ряд 1);
> - **ИИ** — `autocontrol_skeleton` (seg002:685): меч у скелета вынут всегда.
>
> Регресс: `tests-host` зелёные, `t_char` вырос до 65 проверок (добавлены два
> сценария возрождения). Цена: `_CODE` +80 Б, банк 1 (`guards`) 2342 → 2519,
> банк 4 (`gdraw`) 3875 → 4269.
>
> **Не проверено вживую:** сцена требует пройти уровень 3 до кнопки, которая
> открывает выход — без открытой двери скелет по условию не встаёт.
**Что нужно:**
- `charid_4_skeleton` в `enter_guard`/`pop_guard_enter` (seg002:196): при
`tbl_guard_type[level] == 2` персонаж поднимается **с вынутым мечом** и
последовательностью `seq_63_guard_active_after_fall`, а не
`seq_77_guard_stand_inactive`;
- скелет **бессмертен** — чит `K` его уже не берёт (`pop_guard_kill`
проверяет `CHARID_4_SKELETON`), но и боёвка должна возвращать его к
жизни (seg002:252);
- **новый атлас**: `../SDLPoP/data/SKEL/` (29 файлов) — `pop_pack_guard.py`
сейчас прибит к `GUARD/`, нужен параметр набора (`tbl_guard_dat` =
GUARD/FAT/SKEL/VIZIER/SHADOW). Здесь же удобно закрыть
[BUG-GUARD-COLOR-1](bug_list.md#bug-guard-color-1): палитра стража
подменяется по `guards_color`, и оба изменения живут в одном упаковщике.
### <a id="l3-color"></a>L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный) ### <a id="l3-color"></a>L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный)
**Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В **Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В
@@ -315,7 +294,7 @@ offset 22790, **240 байт** = 5 палитр × 16 цветов × 3 байт
**Грабли, уже пойманные на цвете стражей:** `gfx_pal_load` отдаёт указатель **Грабли, уже пойманные на цвете стражей:** `gfx_pal_load` отдаёт указатель
в BIOS (`$A4` через `rst #0x08`), а BIOS читает только `#4000-#BFFF` — в BIOS (`$A4` через `rst #0x08`), а BIOS читает только `#4000-#BFFF` —
таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в таблицу нельзя передавать прямо из банка (0xC000+), надо копировать в
W1/W2 (см. [BUG-GUARD-COLOR-1](bug_closed.md#bug-guard-color-1)). W1/W2 (см. [BUG-GUARD-COLOR-1](BUGS_CLOSED.md#bug-guard-color-1)).
### <a id="draw-cost"></a>DRAW-COST. Кадр НЕ УКЛАДЫВАЕТСЯ в бюджет — нужна оптимизация ### <a id="draw-cost"></a>DRAW-COST. Кадр НЕ УКЛАДЫВАЕТСЯ в бюджет — нужна оптимизация
@@ -660,165 +639,6 @@ tp/10 у факелов таблицей, пустой слот соперник
(клинок/брызги), а не по всему окну; кэш «в этом тайле fore-слоя нет (клинок/брызги), а не по всему окну; кэш «в этом тайле fore-слоя нет
вовсе»; считать окно клипа в тайловых координатах один раз. вовсе»; считать окно клипа в тайловых координатах один раз.
### <a id="draw-char"></a>DRAW-CHAR — **ЗАКРЫТА И ПРОВЕРЕНА 2026-08-08**
Отрисовка сведена к одному набору функций над `Char` (`pop_cdraw.c`), проход
окклюзии — один на всех (`pop_fore_over_char`). Разбор, таблица «было →
стало», пять починенных расхождений и замеры — в
[`TASKS_CLOSED.md`](TASKS_CLOSED.md#draw-char). Оттуда же список того, что
надо потрогать вживую при приёмке уровней.
### <a id="l3-pass"></a>L3-PASS. Приёмка уровня 3 — обход всех комнат
Как L1-PASS/L2-PASS: сквозной проход руками плюс обход комнат читом ROOMNAV.
Осмысленна ТОЛЬКО после L3-CHOMP и L3-SKEL — без них уровень заведомо
неполон, и половина наблюдений будет «механики нет».
Что уже снято и пригодится (справка ниже): комнаты **23 и 24 недостижимы** и
полностью пусты — баги в них не в приоритете; **чекпойнт** уровня 3 сделан
([L3-CHKP](TASKS_CLOSED.md#l3-chkp)), его тоже надо потрогать вживую: уйти
влево из комнаты 7, умереть, проверить респавн в комнате 2 и снятую
loose-плиту (7, кол 4, ряд 0).
### <a id="rooms-graph"></a>Справка: связность комнат уровней 1–3 (снято 2026-08-05)
Обход графа `roomlinks` (@1952, по 4 байта на комнату: L, R, U, D; 0 = нет
соседа) от стартовой комнаты — тем же методом, которым на уровне 1 нашлись
13/18/24 (см. «НЕ БАГИ» в [`bug_closed.md`](bug_closed.md)). Скрипт разовый,
в репозиторий не клался: чтение трёх массивов, BFS и проверка симметрии.
| уровень | старт | недостижимы | признак |
|---------|-------|-------------|---------|
| 1 | к.1 (0,0) | **13, 18, 24** | ссылки наружу есть, обратных нет |
| 2 | к.5 (1,3) | **нет** | граф полностью симметричен, все 24 достижимы |
| 3 | к.9 (2,4) | **23, 24** | то же, что на 1: односторонние ссылки, обе комнаты **полностью пустые** |
```
ур.3: 23 L→4, у 4 R=22 | 24 L→2, у 2 R=7
23 R→22, у 22 L=4 | 24 R→7, у 7 L=2
| 24 U→16, у 16 D=0
на 23 ссылается только 24, на 24 — только 23; тайлы обеих = все empty
```
То есть на уровне 3 это даже более чистый случай, чем на уровне 1: там в
брошенных комнатах была геометрия, здесь — пустота. Практический вывод тот
же: **в 23/24 возможен «мусор в шве»** (наш рендер кромки читает крайнюю
колонку соседа ПО ССЫЛКЕ, а сосед соседом себя не считает), приоритет багов
там низкий, в игре их не видно.
**Уровень 2 — недостижимых нет, но есть три КОЛОДЦА без выхода** (единственная
связь — вверх, откуда Кид падает):
```
к.10 U→4 пики(2,2) + пол — падение из комнаты 4
к.14 U→21 шахта 2 тайла шириной, дно = обломки
к.17 U→15 то же
```
Это не баги данных: в 14/17 попадают только падением насмерть, а из 10
(если выжил) выхода нет вовсе — так в оригинале. При приёмке не считать
«застрял» багом.
---
### <a id="l4-mirror"></a>L4-MIRROR. Зеркало уровня 4 и тень — **АКТИВНА с 2026-08-10**
Приоритет переставлен пользователем: palace-уровни были отложены решением
2026-08-04, но уровень 4 уже гоняется в MAME, и зеркало — единственное, что
мешает пройти его сюжетно.
**Как это работает в оригинале** (SDLPoP, всё сверено по коду):
| что | где | суть |
|---|---|---|
| постановка тайла | `animate_leveldoor`, seg007:0457 | дверь выхода ДОРИСОВАЛА открытие (`modif >= 43`, `leveldoor_open` 0/2 -> 1) -> в комнату 4, колонку 4, ряд 0 пишется `tiles_13_mirror` |
| отражение | `check_mirror`, seg003:0798 | пока Кид на тайле зеркала — каждый кадр рисуется зеркальная копия: `Char.x = (xpos<<1) - Char.x`, `direction` инвертируется, копия идёт в objtable типом 4 с клипом `left = (curr_col<<5)+9`, `top = y_clip[row+1]` |
| прыжок сквозь | seg004:0240 | засчитывается ТОЛЬКО `run-jump` СПРАВА НАЛЕВО (`frame 39..43`, `direction < 0`) -> `modif = 0x56` (разбитое), `jumped_through_mirror = -1` |
| рождение тени | `jump_through_mirror`, seg003:080A | Кид копируется в слот Guard как `charid_1_shadow`; `guardhp = hitp_max`, у Кида `hitp_curr = 1` |
| поведение тени | `autocontrol_shadow_level4`, seg002:1131 | в зеркальной комнате при `x < 80` исчезает (`clear_char`), иначе бежит вперёд. Боя нет |
| клип тени | seg008:1699 | `obj_clip_left = 137 + (mirror_column-4)*32` — тень видна только СПРАВА от зеркала |
| коллизия | `wall_type`, seg006:1632 | зеркало = 2 «стена слева» — **уже портировано** (`pop_map.c`), как и спецкейс `can_climb_up` |
**Шаги.**
1. **АТЛАС — СДЕЛАНО 2026-08-10.** `tile_table[0x0D]` = база 75, фронт 77
(наша таблица совпадает с SDLPoP такт в такт). Тайла 13 **нет ни в одном
уровне статически** — проверено перебором всех 15 `res200N.bin`, ноль
попаданий; санити-проверка разбора: ур.1 без чомпера, ур.3 с 18, ур.4 с
паласными 25..29. Значит `render_room` эти id не увидит и на месте
зеркала был бы чёрный провал (memory `pop_atlas_dynamic_ids`). Добавлены
`MIRROR_ENV_IDS = {75, 77}` в `pop_pack_bg.py`, 77 — ещё и в
`FORE_ENV_IDS`. Оба набора переупакованы: fore 17 -> 18 спрайтов, все
страницы EMM в пределах 16 КБ.
2. **ПОСТАНОВКА ТАЙЛА — СДЕЛАНО И ПРОВЕРЕНО ПОЛЬЗОВАТЕЛЕМ 2026-08-10.**
`place_mirror()` в `pop_trob.c`, вызывается из `animate_leveldoor` по
переходу `pop_leveldoor_open` 0/2 -> 1 (условие оригинала — иначе тайл
ставился бы заново каждый кадр открытой двери). Если комната зеркала уже
на экране, ставится `POP_RD_FLOOR` на обе страницы.
Зеркало появляется, спрайты из атласа читаются. Остался незакрытый
краевой вопрос: не устареет ли `g_fg` коллизии, если игрок окажется в
комнате 4 ровно в момент постановки (в обычном прохождении дверь в другой
комнате).
3. **ОТРАЖЕНИЕ.** Самый дорогой шаг: это ЧЕТВЁРТЫЙ рисуемый Char помимо
Кида и соперника, со своим клипом. Отрисовка у нас уже общая
(`pop_cdraw.c`, слоты — memory `pop_char_draw_unified`), вопрос в числе
слотов и в том, что отражение меняется вместе с Кидом каждый кадр, то
есть `pop_char_skip_mask` на нём не сработает. ЧИСТО КОСМЕТИКА —
делается последним.
4. **ПРЫЖОК СКВОЗЬ + РОЖДЕНИЕ ТЕНИ — СДЕЛАНО 2026-08-10, НЕ ПРОВЕРЕНО В MAME.**
- `is_obstacle` (`pop_map.c`): ветка зеркала (seg004:0239) — Кид, кадры
бегового прыжка 39..43, направление влево -> `modif = 0x56`,
`pop_jumped_mirror = -1`, препятствия нет.
- `mirror_image` / `jump_through_mirror` / `pop_check_mirror`
(`pop_map.c`, порт seg003:0798..08A9). Отражённый Char уходит в слот
Guard как `CHARID_1_SHADOW`; `guardhp = hitp_max`, у Кида `hitp_curr = 1`.
`savekid` НЕ делается — как в оригинале.
- `pop_check_mirror()` зовётся из главного цикла ПЕРЕД отрисовкой
персонажей (в оригинале — первая строка `draw_people`).
- `autocontrol_shadow` + `autocontrol_shadow_level4` + `clear_char`
(`guards.c`, seg002:081D/1131/seg006:1945). Тень идёт СВОЕЙ веткой
целиком: к стражьему ИИ она не сводится, на ур. 4 не дерётся вовсе.
- **АТЛАС ТЕНИ.** Вскрылось при чтении seg006:0532: тень вне боевых
кадров 150..189 ходит по таблице КИДА, и `image` оттуда индексирует
спрайты Кида, а не стража. Выбор атласа в `pop_cdraw` шёл по СЛОТУ —
тень рисовалась бы спрайтами стража. Условие вынесено в
`pop_frame_tbl_is_guard()` (`pop_kid.c`), его теперь читают и
`load_frame`, и отрисовка — разъехаться не могут.
- Звука (`sound_45_jump_through_mirror`) в порте нет, пропущен.
Цена: `_CODE` +18 Б, банк 1 2311 -> 2367, банк 3 10328 -> 10551,
банк 4 8243 -> 8267.
**Что проверить в MAME:** на ур. 4 открыть дверь выхода, дойти до
комнаты 4, разбежаться СПРАВА НАЛЕВО и прыгнуть в зеркало — должна
появиться тень (спрайтами Кида, зеркально) и убежать влево, растворившись
при `x < 80`. У Кида после этого 1 HP, у тени полная полоса.
5. **КЛИП ТЕНИ** (seg008:1699): `obj_clip_left = 137 + (mirror_column-4)*32`
— тень видна только СПРАВА от зеркала. **Не сделано и не однострочник:**
в `pop_cdraw` есть клип сверху, снизу и СПРАВА (`vis_w`), а левого нет
вовсе — `gfx_blit_cols_part_w` умеет ограничить ширину, но не пропустить
колонки слева. Нужен либо новый примитив, либо сдвиг `bx` со срезом
исходных колонок. Пока без него тень будет вылезать левее зеркала.
6. **ВИД ТЕНИ (два блиттера OR + XOR) — ОТЛОЖЕН 2026-08-11 по решению
пользователя.** Пока тень рисуется как обычный персонаж, простой копией
из атласов Кида — сюжетно уровень это не задерживает. Все изыскания
собраны в [`../docs/shadow_render.md`](../docs/shadow_render.md):
почему XOR несовместим с нашей прозрачностью `#FF` (нужен источник с
прозрачным `0x00`), почему два прохода оригинала не воспроизводятся в лоб
(операция читает ОЗУ-копию, а персонажи рисуются банком 0x5C) и во что
это выливается — однопроходный композит `s | bg ^ s(сдвиг)`. Там же
ЗАМЕР стоимости подготовки такого источника в рантайме (145.3 такта/байт:
0.11 с на страницу, 1.3 с на реальные 186 КБ атласа) и четыре варианта.
Блочные операции акселератора для этого уже есть в libbgi
(`gfx_blit_cols_op` и семейство, `tests/accop` 10/10 PASS).
Вернуться, когда будут сделаны все уровни и станет известно, какими
кадрами тень реально пользуется (ожидание: бег, длинный прыжок из
зеркала, питьё зелья, боёвка — тогда готовить надо единицы страниц).
Порядок: 1 -> 2 -> 4 -> 5 -> 3. После 4 и 5 уровень уже проходится, потому
что сюжетно достаточно прыгнуть сквозь зеркало.
---
## P1 — берётся в любой момент ## P1 — берётся в любой момент
### <a id="l1-speed"></a>L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01) ### <a id="l1-speed"></a>L1-SPEED. Игра идёт быстрее оригинала (найдено 2026-08-01)
@@ -889,9 +709,9 @@ HP/минуты, номера «особых» комнат и уровней (
где баг» вместо ручной подгонки позы. где баг» вместо ручной подгонки позы.
- **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует. - **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует.
- **Таймер уровня / HUD времени / меню / сохранения** — Фаза 6. - **Таймер уровня / HUD времени / меню / сохранения** — Фаза 6.
- **[T-1](bug_list.md#t-1)** (пики: перерисовка по причине) — отдаётся почти - **[T-1](BUGS_OPEN.md#t-1)** (пики: перерисовка по причине) — отдаётся почти
бесплатно после [T-2](bug_list.md#t-2), отдельно не окупается. бесплатно после [T-2](BUGS_OPEN.md#t-2), отдельно не окупается.
- **Отключение мыши на время игры** и **замена PRNG** - **Отключение мыши на время игры** и **замена PRNG**
`../docs/ideas_backlog.md` (оба дают доли процента кадра). `../docs/ideas_backlog.md` (оба дают доли процента кадра).
- **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость - **OPT-1** (хирургический редрой шва) — решено НЕ делать, стоимость
транзиентная; разбор в [`bug_closed.md`](bug_closed.md). транзиентная; разбор в [`BUGS_CLOSED.md`](BUGS_CLOSED.md).
-80
View File
@@ -1,80 +0,0 @@
Активные баги на уровне 1 -
- После того как кувшин со здоровьем выпит и Кид попадает в комнату во второй раз
то иногда кувшин мы не показываем (это правильно) но показываем анимацию пузырьков над
кувшином (чего показывать больше не должны - нет кувшина, нет пузырьков),
а в комнате 14 пузырек вообще восстанавливается полностью после того как Кид
выйдет из комнаты и вернется в нее обратно (есть ощущение что даже в одной комнате
поведение пузырька - будет ли он восстановлен или нет зависит от чего-то непонятного.
(есть еще ощущение что первый выпитый кувшин не восстанавливается а остальные
восстанавливаются)
- Меч - один раз подобранный он больше не должен появляться в своей (15-ой) комнате
иногда он появляется снова, иногда он больше не появляется но на дальней стене начинают
моргать контуры кирпичной кладки (странно что она не впечатана в heal уровень потому
что она должна быть отрисована всегда). причем мерцает по странному - не как double-buff
а как буд-то вместо искры на мече - изредка на несколько (один логический?) кадров.
- комната 12 - опять проблема с телепортацией через решетку - решетка в 0,9 - Кид стоит
за шаг (пол-шага) от решетки - бежит и пробегает через закрытую решетку.
- после respawn - остаются разбитыми плиты что были разбиты, выпитыми пузырьки и прочее -
это надо сравнить с оригиналом (SDLPoP) - но главное другое - если до respawn какая-то
плита не была разбита то теперь если ее разбить, выйти из комнаты и вернуться - то
плита восстановится (еще интереснее вариант когда плита-потолок в комнате 12 разбивается,
Кид через проем поднимается в верхнюю комнату и поднявшись плита в новой комнате опять
закрывает проем.
- проблема с плитой из комнаты 12 в верхнюю и пузырек в комнате 22 - они возникают даже
без respawn - когда Кид заходит в комнату 22 из комнаты 16, выпивает пузырек, выходит опять
в комнату 16 и опять возвращается в комнату 22 - пузырек возникает снова. Когда Кид разбивает
плиту из комнаты 12 в комнату 16 то после подъема в комнату 16 плита опять возникает.
аналогично комната 17 - если войти в нее из комнаты 23, разбить плиты 1,5 и 1,6, опуститься на
2,3 и выпить пузырек. вернуться в комнату 23 и опять в 17 - то и плиты вернутся и пузырек.
но могут плита и пузырек не вернуться - такое ощущение что если это первая разбитая плита
(Кида мы перенесли в комнату 12 через cheats) - то разбитая плита остается разбитой.
- комната 6 - Кид бежит по уровню ноль справа налево (начало бега 0,9), делает длинный
прыжок между 0,6 и 0,7 - он пролетает во внутрь стены и респавнится - скриншоты в bugscreens
- иногда залипают все-таки клавиши (пока замечено на кнопке вверх - отлипает только после
следующего нажатия). надо проверить - есть ли возможность не только отслеживать события
нажатие/отжатие но и текущий статус нажатых кнопок. Может вообще отказаться от прямой работы
с клавиатурой а работать с нею через BIOS ?
- иногда таки залипает и shift =((((
- комната 5 - кнопка на 0,4 не открывает решетку/ворота на 0,5 - это двойная кнопка - она
должна сразу открывать ворота на 0,5 и на 0,9 (возможно у нас она работает только на 0,9)
возможно что логически решетка на 0,5 открывается но анимации ее открытия нету =(
- комната 5 - Кид падает с кнопки 0,6 на 2,7 - получает урон, присяд в 2,8 - когда встает
он проваливается еще ниже в комнату 6 на между 2,7-2,8.
- если стражник убивает Кида то Кид респавнится на месте смерти и стражник тут же его убивает
еще раз и так по циклу
--------
- проверить как отрисовывается Кид вместе с мертвым стражником - в оригинале стражник поверх
Кида (Кид проходит за телом стража), у нас Кид поверх (перед) стражем;
- сейчас у нас шифт не залипает но теперь он теряет статус нажатости - то есть при нажатом шифте
второе-треть-четвертое нажатие стрелки отрабатывает не как осторожный шаг а переводит Кида на бег
по идее с новым алгоритмом должно было бы корректно отрабатывать нажатие шифта ? теперь же залипаний
нету но есть фантомный отжим ? такое поведение работы с клавиатурой хуже чем залипание шифта (потому
что игрок надеется что шифт нажат и будет короткий шаг а Кид начинает бежать и может провалиться в
ловушку/яму или набежать на пики и погибнуть). то есть надо как-то более точно проверять статус нажатости
шифта.
то есть - когда нажат шифт и нажимаются стрелки то вариант редких пропусков стрелок предпочтительнее чем
вариант когда мы перестаем понимать что шифт нажат.
- после respawn - должны ли оживать стражники ? сейчас они остаются мертвые.
- небольшой баг отображения (такой баг у нас уже был и ты его фиксил) - комната 12 - две падающие плиты
рядом (0,1 и 0,2) - после падения осколки только от одной на 1,2 а от 0,1 на 1,1 осколков нету.
при том что в комнате 7 две плиты 0,5 и 0,6 падая на 2,5 и 2,6 дают двое осколков.
возможная причина - когда Кид пробегает в комнате 12 по падающим плитам то он убегает сразу в комнату 15
и плита 0,2 успевает упасть и поменять статус пола 1,2 на пол с осколками а плита 0,1 еще остается в падении
когда Кид уже уходит в комнату 15 и потому статус пола на 1,1 на 'пол с осколками' измениться не успевает
--------
Подумай вот над чем -
интересует возможность покрытия уже написаного кода автотестами (тесткейсами).
автотесты внутри MAME я думаю будут достаточно затруднительными, но поскольку это у нас plain-C
то можно наверное делать автотесты и на host OS.
возможно это нам поможет не создавать ситуации когда нам каждый раз приходится фиксить баги которые
мы уже фиксили (по крайней мере постараемся избегать регресса при реализации новых фич)
я понимаю что это скорее всего будет достаточно сложно совместить с тем что финальный продукт идет
для Sprinter. но подумать над этим надо.
Пока ничего не предпринимай, только подумай что и как можно сделать.
-10
View File
@@ -1,10 +0,0 @@
Баги/недоработки на уровне 2
- комната 4 - бой со стражем - страж другого цвета/другого цвета у него HP (сравнение с SDLPoP)
- комната 4 - бой со стражем - при нанесении урона стражу должен рисоваться сплеш (типа звезды) - что бы игрок
видел что удар дошел до цели без перевода внимания на HP (
- баг нашего cheats - если мы нажимаем +/- (переход по комнатам) в момент когда Кид вытащил меч для битвы -
Кид теряет управление (он переключается в режим боя но в новой комнате нету стража - в результате нажатия
кнопок движения игнорятся)
+1 -1
View File
@@ -33,7 +33,7 @@ void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down) __
* сквозь закрытые ворота шва BUG-GATE-PASS-1). dcol на сколько * сквозь закрытые ворота шва BUG-GATE-PASS-1). dcol на сколько
* изменились номера колонок: ушли ВПРАВО 10, ВЛЕВО +10. Звать СРАЗУ после * изменились номера колонок: ушли ВПРАВО 10, ВЛЕВО +10. Звать СРАЗУ после
* pop_map_set_edges и только для боковых переходов; вверх/вниз и все прочие * pop_map_set_edges и только для боковых переходов; вверх/вниз и все прочие
* входы в комнату остаются на полной инвалидации. Разбор bug_closed.md, * входы в комнату остаются на полной инвалидации. Разбор BUGS_CLOSED.md,
* расхождение с дословным портом ../docs/impl_diff.md. */ * расхождение с дословным портом ../docs/impl_diff.md. */
void pop_coll_shift(int8_t dcol) __banked; void pop_coll_shift(int8_t dcol) __banked;
+1 -1
View File
@@ -642,7 +642,7 @@ void pop_process_trobs(uint8_t cur_room) __banked
* безусловно, мы только когда там чомпер. Безусловная * безусловно, мы только когда там чомпер. Безусловная
* пометка = перерисовка тайла каждый кадр на каждый факел, * пометка = перерисовка тайла каждый кадр на каждый факел,
* а слой anim рисует ещё пики/зелье/меч (seg008:644). Их * а слой anim рисует ещё пики/зелье/меч (seg008:644). Их
* случай «застыл справа от факела» в bug_list как открытый: * случай «застыл справа от факела» в BUGS_OPEN.md как открытый:
* пики и меч рядом с факелом на уровнях 1-4 не встретились. */ * пики и меч рядом с факелом на уровнях 1-4 не встретились. */
if (trob_rcode[i] == TILE_CHOMP) if (trob_rcode[i] == TILE_CHOMP)
pop_set_redraw((uint8_t)(tp + 1), POP_RD_CHOMP, 1); pop_set_redraw((uint8_t)(tp + 1), POP_RD_CHOMP, 1);
@@ -112,7 +112,7 @@ pop_loose_tick(); // досчёт тряски и снятие провалив
потребовалось — карту комнаты в него ИНЪЕКТИРУЮТ через `pop_map_set`, — потребовалось — карту комнаты в него ИНЪЕКТИРУЮТ через `pop_map_set`, —
но набор на сам `pop_level`/`pop_trob` в это упрётся. Решение то же, что но набор на сам `pop_level`/`pop_trob` в это упрётся. Решение то же, что
для `kid_data`: положить страницу по нужному адресу и поднять `CODE_LOC`. для `kid_data`: положить страницу по нужному адресу и поднять `CODE_LOC`.
- **Сценарии из `bug_closed.md`.** У каждой закрытой записи есть симптом и - **Сценарии из `BUGS_CLOSED.md`.** У каждой закрытой записи есть симптом и
ожидаемое поведение — готовый список регрессионных кейсов, который стоит ожидаемое поведение — готовый список регрессионных кейсов, который стоит
переносить в `t_phys` по мере надобности. переносить в `t_phys` по мере надобности.
- **BUG-LOOSE-2** (гонка «уйти из комнаты раньше, чем долетит плита») — - **BUG-LOOSE-2** (гонка «уйти из комнаты раньше, чем долетит плита») —