# Точка входа для следующей сессии (обновлено 2026-08-12, ночь) Файл для старта с чистого контекста: где всё стоит, что делать первым, какие грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам: `TASKS_OPEN.md` (доска), `BUGS_OPEN.md` (открытые баги), `CLAUDE.md`. --- ## 1. СЛЕДУЮЩАЯ ЗАДАЧА: smoke-прогон уровня 9 и регресс 1-8 Переворот (зелье инверсии) **доведён и принят пользователем 2026-08-12**: фон, персонажи, клинок, полоса HP, факелы, окклюзия передних граней и переходы между комнатами работают зеркально; чит `U` и зелёное зелье идут одним путём. Осталось пройти сам уровень 9 живьём и убедиться, что на уровнях 1-8 ничего не поехало (весь слой фона теперь спрашивает `pop_upside`). Что смотреть на прогоне в первую очередь: - **вис на кромке и спуск в перевёрнутом виде** — это новый срез `clip_char` (он режет кадр СНИЗУ вместо верха); тело должно уходить за кладку, а не ложиться поверх неё; - **бой со стражем перевёрнутым** — у соперника свой зеркальный набор (`g*_v.atl`), а тень рисуется атласами Кида, отдельных файлов ей не надо; - **зелье в другой комнате** — переворот перерисовывает не комнату, а чистит факелы и отражает страницу; проверить, что после смены комнаты и возврата фон остаётся чистым. Открытый риск (пока не проявился, но проверить осознанно): имена зеркальных атласов `kid10_v.atl`…`kid27_v.atl` — **9 символов до точки, а DSS работает по 8.3**. Если часть кадров Кида в перевёрнутом виде рисуется обычными (неперевёрнутыми) — это оно; лечится переименованием в `pop_pack_kid.py` (например `k10v.atl`) плюс те же имена в `pop_kboot.c`. Дальше по плану — уровни 10/11 (по анализу нового в них нет) и [MEM-COLD2](TASKS_OPEN.md#mem-cold2) п.3, если снова понадобится место. Ощущенческий хвост прежний — [GRAB-KBD-TIMING](BUGS_OPEN.md#grab-kbd-timing), разбирается после готовности всех уровней (решение пользователя). ## 2. Состояние репозитория Всё закоммичено, `main` чист. `tests-host`: **6 наборов** проходят (`[char] 65`, `[geom] 3144`, `[grab] 55`, `[mouse] 17`, `[phys] 1727`, `[wall] 1`). Коммиты 2026-08-12 (сверху — свежие): | хеш | что | |---|---| | `7f5c99b` | чистка факелов при перевороте — в СТАРОЙ системе координат | | `c9aa6d9` | переворот: не перерисовывать комнату, стирать только запечённое пламя | | `948d8f0` | переворот: перерисовка вместо отражения, переключение на границе кадра, `clip_char` | | `5b6664f` | зеркальные кадры персонажей — готовыми файлами `*_v.atl` | | `c4dd2c7` | отсев тайлов по fore-окну — в ЛОГИЧЕСКИХ координатах | | `7551906` | L9-INVERT II.4: персонажи рисуются перевёрнутыми | | `055d6d7` | чтение файлов нельзя выносить в банк (код банка сам живёт в W3) | | `147cb18` | резидент: `pop_kid`/`pop_guard`/`main` расколоты по частоте вызова | | `5d31ed0` | резидент: `pop_redraw` + холодная половина `pop_level` в банки | | `f541c0a` | клип поля при перевороте (мусор в бортах) | Уровни 1-8 проходятся, уровень 9 ждёт прогона. ## 3. Раскладка кода после разгрузки резидента Куча выросла **902 → 5795 Б** (`_CODE` 25 628 → 20 569). Кандидатов выбирали не по размеру, а по тому, КТО зовёт: если единственный вызывающий уже в банке, код едет к нему и трамплин не появляется вовсе. | банк | модули | |---|---| | 1 | `guards.c` | | 2 | `pop_bg.c` | | 3 | `pop_map.c` | | 4 | `pop_cdraw.c` + **`pop_kdraw.c`** (клинок, блит по id — прямые вызовы) | | 5 | `pop_ctrl.c` | | 6 | `pop_trob.c` | | 7 | `pop_room.c` + **`pop_redraw.c`** | | 8 | `roomtest_cold.c` + **`pop_level_cold.c`** + **`pop_kboot.c`** + **`pop_guard_cold.c`** | Внутренние контракты: `_pop_level.h`, `_pop_kid.h`, `_pop_kdraw.h`. В них же записаны два правила, которые линкер НЕ проверяет: 1. **Банковый модуль не маппит W3** — он в этом окне и исполняется (через W0 можно). Поэтому `pop_level_read_file` и `pop_kid_data_load` остались в резиденте: первый же вызов из банка улетал в halt по мусору. 2. **Прямые вызовы между модулями одного банка** корректны только пока они действительно в одном банке; меняешь раскладку в Makefile — проверь пары. ## 4. Переворот (L9-INVERT) — как он устроен - **Флаги:** `pop_upside_want` (пишут зелье, смерть Кида, чит `U`) и `pop_upside` (читают все слои). Переключение — ОДНО место, начало кадра, вместе с `pop_flip_screen`. Оригиналу этого не нужно (он рисует в offscreen неперевёрнутым и зеркалит на выводе) — расхождение записано в `../docs/impl_diff.md`. - **Сам переворот:** чистка запечённого пламени (`pop_torch_wipe`, 0..4 тайла) + отражение страницы акселератором + прямая копия во вторую. **904 247 тактов = 2.1 кадра.** Вариант с полной перерисовкой комнаты (как `redraw_screen` в оригинале) стоил 6 245 477 = 14.5 кадра — отвергнут. - **Спрайты персонажей:** column-major, вертикальное зеркало для них не бесплатно, поэтому второй набор файлов делает упаковщик (`vflip_cols` в `pop_pack_kid.py`). Загрузка одной страницы с диска — 970 003 такта, разворот той же страницы в рантайме — 12 645 582; поэтому файлы. `pop_vflip_load_all()` идемпотентна и зовётся откуда угодно (сейчас — старт и смена уровня, если это `POP_UPSIDE_LEVEL`; задел под загрузку в интро). - **Координаты:** у окна fore-клипа ДВЕ пары границ — экранная (`pop_t_fclip_y0/y1`, по ней режется блит) и логическая (`pop_t_fclip_ly0/ly1`, по ней идут ВСЕ отсевы тайлов). Смешать их — и передние грани перестают перекрывать персонажа. ## 5. Состояние окружения - **MAME запущена с мостом**, на HDD — roomtest с `LEVEL=9`. Стартовый уровень теперь **параметр сборки**: `make LEVEL=9` (дефолт 9; для «настоящей» игры — `LEVEL=1`), константа объявлена в `pop_tune.h`. - **ПРАВИЛО ПОЛЬЗОВАТЕЛЯ: перед сборкой образа — `make clean`, потом `make`, потом `make hdd`** (memory `mame_build_clean_rule`). После пересборки образа MAME **обязана** пройти полный рестарт (memory `mame_hdd_rebuild_restart`): остановка через `exit` в дебаггере, затем `run_bridge.sh`. - **Тайминги моста** (правило пользователя, длиннее не ждать; они же в `docs/mame-autotest.md` §10): старт `run_bridge.sh` → **6 с** → `go` → **8 с** → `keyseq d:{ENTER}` + `keyseq roomtest{ENTER}` → **5 с** → программа работает. Весь цикл перезапуска ~20 секунд. - **MCP-инструменты `mame-z80` работают** (в отличие от прошлой сессии): `press_key`, `screenshot`, `read_logical_memory`, `debugger_command`. Профилирование — `bpset ` + `print totalcycles`, затем `out` и снова `print totalcycles`; кадр = 430 000 тактов. - Адреса состояния смещаются после КАЖДОЙ пересборки — брать из `.sprinter-cc-roomtest/roomtest.map` заново. Банковые функции лежат по `0x0008XXXX`; для брейкпоинта брать младшие 16 бит (`0xC912` и т.п.). - **SDLPoP собран с отладочной информацией** (`-O0 -g3`). - **НЕ ПРИБРАНО:** в `SDLPoP/src/seg008.c` диагностический `fprintf` с меткой `DBGMIRROR` в начале `add_objtable`. ## 6. Остальное открытое - **[FORE-DUP](BUGS_OPEN.md#fore-dup)** — передний слой тайла рисуется дважды при перекрытии объектов. Картинку не портит, тратит такты. Сначала замерить, потом чинить. - **TORCH-ANIM-RIGHT** — под запечённым пламенем могут застыть пики/меч/зелье справа от факела. - **[GATE-FORE-KID](BUGS_OPEN.md#gate-fore-kid)** — Кид в проёме ворот виден поверх решётки; портирован только шовный случай. - Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1`, `L3-COLOR`, `L1-SPEED`, `TUNE-1`. - **Вид тени (OR+XOR) отложен** до готовности всех уровней — [`../docs/shadow_render.md`](../docs/shadow_render.md). ## 7. Грабли, собранные 2026-08-12 (вечер/ночь) - **`return 1;` из ветки у SDCC 4.5 может не положить единицу в A** — функция возвращает мусор. Годами маскировался (в A случайно оставался ненулевой байт) и вылез ровно на `__banked`. Репро — `docs/bugs/sdcc-z80-ret-const-lost/`, обход — один выход через переменную. Аудит всех `.asm` roomtest: других мест нет. - **Банковый модуль не может маппить W3** (см. §3). Симптом — halt по мусору в 0xC4xx на первом же вызове. - **Логические и экранные координаты при перевороте — разные системы.** Три бага подряд из одного корня: отсев тайлов по fore-окну, `clip_char`, чистка факелов. Правило: всё, что считается ИЗ РЯДА комнаты (`63*row + …`), — логическое; всё, что режет блит, — экранное. - **Пламя факела ЗАПЕКАЕТСЯ в ОЗУ-копию** (heal'а у него нет, следующий кадр накрывает предыдущий непрозрачно). Любое отражение/копирование страницы тащит его с собой — перед такими операциями звать `pop_torch_wipe`. - **`pop_upside` переключать в середине кадра нельзя**: остаток кадра рисуется зеркально поверх ещё неперевёрнутой картинки, и запечённые транзиенты остаются в комнате навсегда. - **Перенос константы между файлами — потерянное значение.** При выносе `FIRST_LEVEL` в `pop_tune.h` записал дефолт 1, и игра молча стартовала с первого уровня вместо девятого; заметил пользователь. Теперь это параметр сборки (`make LEVEL=N`). - **`make -C tests-host` не пересобирает по изменению заголовков** — при правке прототипов (особенно `__banked`) обязателен `make -C tests-host clean`, иначе стаб и вызывающий разъезжаются по ABI и тест врёт. ## Грабли 2026-08-10/11 - **Не оценивать железо по своей же memory-заметке.** Дважды поправлял пользователь: accel умеет не только копирование (есть AND/OR/XOR) и умеет работать с обычной RAM, а не только с видео (`docs/part2/accelerator_doc.txt`: «supports data blocks transfer of RAM and Video-RAM», не поддерживает только ROM и FastRAM). Проверять по драйверу MAME и докам, а не по памяти. - **lldb через FIFO — плохая идея.** Работает надёжно: `fprintf(stderr, …)` прямо в SDLPoP, пересборка (секунды) и чтение stdout. - **Доска отставала от кода на три задачи.** Перед планированием — сверять `TASKS_OPEN.md` с кодом грепом, а не верить записи. - Диапазон `obj_x` = **−416..695** (посчитан из `kid_data.bin`).