NEXT_SESSION: итоги дня — разгрузка резидента и переворот приняты

Задача на завтра: smoke-прогон уровня 9 + регресс уровней 1-8 (слой фона
теперь весь спрашивает pop_upside).  Записаны раскладка кода по банкам после
разгрузки (куча 902 -> 5795 Б), устройство переворота с замерами, два
правила, которые линкер не проверяет (банк не маппит W3; прямые вызовы
только внутри одного банка), новые тайминги моста и грабли дня — включая
баг SDCC с потерянным `return 1` и разницу логических/экранных координат
при перевороте.

Открытый риск на проверку: имена kid10_v.atl…kid27_v.atl — 9 символов до
точки при DSS 8.3.
This commit is contained in:
2026-08-12 23:49:41 +03:00
parent 7f5c99ba4f
commit 716f6f9273
+147 -110
View File
@@ -1,4 +1,4 @@
# Точка входа для следующей сессии (обновлено 2026-08-12, вечер)
# Точка входа для следующей сессии (обновлено 2026-08-12, ночь)
Файл для старта с чистого контекста: где всё стоит, что делать первым, какие
грабли уже собраны. Читать целиком — он короткий. Дальше по ссылкам:
@@ -6,28 +6,33 @@
---
## 1. СЛЕДУЮЩАЯ ЗАДАЧА: smoke-прогон уровня 8
## 1. СЛЕДУЮЩАЯ ЗАДАЧА: smoke-прогон уровня 9 и регресс 1-8
Зелья приняты пользователем (уровень 2 — «−HP», уровень 7 — перо), уровень 7
считается прошедшим smoke. Спецсобытие уровня 8 —
[L8-MOUSE](TASKS_CLOSED.md#l8-mouse) — **закрыто 2026-08-12**: мышь
портирована, покрыта хост-набором `t_mouse` (17 проверок) и проверена живьём в
MAME (появилась, нажала кнопку (0,7) комнаты 16, решётка (0,3) поехала вверх,
ушла и погасла). Новых тайлов уровень 8 не приносит вовсе.
Переворот (зелье инверсии) **доведён и принят пользователем 2026-08-12**:
фон, персонажи, клинок, полоса HP, факелы, окклюзия передних граней и
переходы между комнатами работают зеркально; чит `U` и зелёное зелье идут
одним путём. Осталось пройти сам уровень 9 живьём и убедиться, что на
уровнях 1-8 ничего не поехало (весь слой фона теперь спрашивает `pop_upside`).
Осталось пройти сам уровень: он длинный, с возвратом через два чомпера
комнаты 4, и стражи там сильнее прежних — комната 5 держит **skill 7**
(максимум из встречавшихся), комнаты 12/22/24 — skill 2/3/3.
Что смотреть на прогоне в первую очередь:
Правило появления мыши (сверено с оригиналом, детали в
[TASKS_CLOSED](TASKS_CLOSED.md#l8-mouse)): счётчик — сам `leveldoor_open`,
растёт только пока Кид в комнате 16, срабатывание ровно на 150, **второго
раза нет**; сбрасывает только рестарт уровня. Тупика это не создаёт — низом
через провал есть путь к двери уровня ценой 1 HP.
- **вис на кромке и спуск в перевёрнутом виде** — это новый срез `clip_char`
(он режет кадр СНИЗУ вместо верха); тело должно уходить за кладку, а не
ложиться поверх неё;
- **бой со стражем перевёрнутым** — у соперника свой зеркальный набор
(`g*_v.atl`), а тень рисуется атласами Кида, отдельных файлов ей не надо;
- **зелье в другой комнате** — переворот перерисовывает не комнату, а чистит
факелы и отражает страницу; проверить, что после смены комнаты и возврата
фон остаётся чистым.
Резидент разгружен ([MEM-COLD1](TASKS_CLOSED.md#mem-cold1)): холодная половина
главного цикла уехала в **банк 8** (`roomtest_cold.c`), куча 879 → **1707 Б**.
Следующие кандидаты — [MEM-COLD2](TASKS_OPEN.md#mem-cold2).
Открытый риск (пока не проявился, но проверить осознанно): имена зеркальных
атласов `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),
разбирается после готовности всех уровней (решение пользователя).
@@ -36,117 +41,149 @@ MAME (появилась, нажала кнопку (0,7) комнаты 16, р
Всё закоммичено, `main` чист. `tests-host`: **6 наборов** проходят
(`[char] 65`, `[geom] 3144`, `[grab] 55`, `[mouse] 17`, `[phys] 1727`,
`[wall] 1`). `make size-check`: OK (65 программ, роста нет).
`[wall] 1`).
Коммиты 2026-08-12:
Коммиты 2026-08-12 (сверху — свежие):
| хеш | что |
|---|---|
| `b5951c9` | зацеп за кромку нижнего ряда: `do_fall` пускает ряд до 3 + `check_grab` в midair |
| `d7d18ae` | зелье медленного падения (перо) + цвет пузырьков по типу зелья |
| `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` | клип поля при перевороте (мусор в бортах) |
Раньше (2026-08-11, вечер): `abe4a83` уровень 5, `740cc6d` арка у ворот,
`e4ef489` L6-SHADOW, `9ecea17` уровень 6 → 7.
Уровни 1-8 проходятся, уровень 9 ждёт прогона.
Уровни 1-6 проходятся.
## 3. Раскладка кода после разгрузки резидента
## 3. Состояние окружения
Куча выросла **902 → 5795 Б** (`_CODE` 25 628 → 20 569). Кандидатов выбирали
не по размеру, а по тому, КТО зовёт: если единственный вызывающий уже в
банке, код едет к нему и трамплин не появляется вовсе.
- **MAME запущена с мостом**, на HDD — roomtest, собранный с
`-DFIRST_LEVEL=8`. **ПРАВИЛО ПОЛЬЗОВАТЕЛЯ: перед сборкой образа —
`make clean`, потом `make`, потом `make hdd`** (memory
`mame_build_clean_rule`). Без этого `make hdd` кладёт на диск ПРОШЛУЮ
сборку: `FIRST_LEVEL` живёт внутри `roomtest.c`, и без изменения исходника
make считает `.exe` актуальным — 2026-08-12 на этом уехал уровень 1 вместо
8, заметил пользователь. После пересборки образа MAME **обязана** пройти
полный рестарт (memory `mame_hdd_rebuild_restart`).
- Адреса состояния сместились после MEM-COLD1: брать из
`.sprinter-cc-roomtest/roomtest.map` заново (`cur_room` 0xA33D,
`pop_leveldoor_open` 0xA34C, `Kid` 0xA3B1, `Guard` 0xA759 — на сборку
2026-08-12). Мост: `setmem ADDR HEXBYTES` (не `write`), `mem ADDR N`.
- **Остановка MAME — `mame/v306/bridge_cmd.sh 'cmd exit'`**, НЕ `kill`
(правило пользователя). `pkill -f` бывает не срабатывает, и остаются ДВА
инстанса, оба отвечающих на файловый IPC — тогда скриншоты приходят от
чужой машины. Проверять `pgrep -f mame.arm` после остановки.
- Прогон без MCP-инструментов: запустить `run_bridge.sh`**sleep 6**
`bridge_cmd.sh 'cont'`**sleep 8**`keyseq d:{ENTER}`
`keyseq roomtest{ENTER}``snap <путь>.png`. Тайминги — правило
пользователя, длиннее ставить не надо. **Имя .exe на образе — не длиннее
8 символов (FAT 8.3) и БЕЗ подчёркивания** (`keyseq` не умеет `_`).
- **Вести игру из моста — по обратной связи из памяти, а не по скриншотам**:
`mem 0xA46A 10` = `pop_char_t` Кида, `mem 0xA3F7 1` = номер комнаты (адреса
из `.sprinter-cc-roomtest/roomtest.map`+`.sym`). Полный рецепт (удержания,
чит-навигация, тайминги нажатий) — memory `mame_mcp_bridge`.
- **MCP-инструменты `mame-z80` не поднимались**: сервер падал на
`ModuleNotFoundError: mcp.server.fastmcp` — вышел `mcp` 2.0, где FastMCP
переехал. В `~/.claude.json` зафиксировано `--with "mcp<2"`; подхватится
при следующем СТАРТЕ сессии Claude (внутри текущей — нет).
- **SDLPoP собран с отладочной информацией** (`-O0 -g3`); pkg-config на
машине нет, команда сборки — в истории этого файла (`git log -p`).
| банк | модули |
|---|---|
| 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 <addr>` + `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` (гейтится по `obj_type == 1 || == 4`).
`DBGMIRROR` в начале `add_objtable`.
## 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,
побайтно). Механика и три ловушки — memory `accel_block_ops`; главные:
операция читает ОЗУ-КОПИЮ экрана (не VRAM), а прозрачность `#FF` совместима
с AND/OR, но не с XOR.
**Вид тени (OR+XOR) отложен** до готовности всех уровней — разбор, замеры и
варианты в [`../docs/shadow_render.md`](../docs/shadow_render.md).
## 5. Остальное открытое
## 6. Остальное открытое
- **[FORE-DUP](BUGS_OPEN.md#fore-dup)** — передний слой тайла рисуется дважды
при перекрытии объектов (Кид+отражение всегда, Кид+соперник — весь ближний
бой). Картинку не портит, тратит такты. **Сначала замерить, потом
чинить** — окно клипа в своё время дало 3.2×.
при перекрытии объектов. Картинку не портит, тратит такты. Сначала
замерить, потом чинить.
- **[DIED-ON-BUTTON](BUGS_OPEN.md#died-on-button)** — `died_on_button`
(seg007:776) не портирован.
- **TORCH-ANIM-RIGHT** — под запечённым пламенем могут застыть пики/меч/зелье
справа от факела; на уровнях 1-4 такого соседства нет.
- **GUARD-PHYS ЗАКРЫТ** 2026-08-11: нажатие напольной кнопки НЕ-Кидом
проверено живьём на уровне 6 (тень встаёт на `closer` и роняет решётку).
справа от факела.
- **[GATE-FORE-KID](BUGS_OPEN.md#gate-fore-kid)** — Кид в проёме ворот виден
поверх решётки; портирован только шовный случай.
- Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1` (оба низкий приоритет),
`L3-COLOR`, `L1-SPEED` (игра на ~39 % быстрее оригинала), `TUNE-1`.
- Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1`, `L3-COLOR`, `L1-SPEED`, `TUNE-1`.
- **Вид тени (OR+XOR) отложен** до готовности всех уровней —
[`../docs/shadow_render.md`](../docs/shadow_render.md).
## 6. Грабли, собранные 2026-08-12
## 7. Грабли, собранные 2026-08-12 (вечер/ночь)
- **Тест, который «проходит» и на сломанном коде, ничего не стоит.** Первая
версия регресса на спуск-зацеп засчитывала ЛЮБОЙ вис — а Кид на первых
кадрах падения цепляется обратно за ту же верхнюю кромку. Признак цели
обязан быть позиционным. Проверять новый регресс откатом фикса — обязательно.
- **`action == 3` (midair) — это не только падение**: кадры спуска `seq_68`
тоже идут с ним. Ждать «начала падения» по одному `action` нельзя.
- **Клавиши, зажатые из моста, переживают точечный `release`**: плагин
переустанавливает удержания каждый кадр, и после серии `hold` осталось 6
незакрытых входов — у пользователя «Кид ходит как с зажатым Shift».
Заканчивать работу с вводом только `pause` + `cont` (release_all).
- **Чит-навигация `+`/`` теряет нажатия и роняет Кида** из комнат без пола:
уровень 7 из комнаты 17 всегда сваливается в 1, и кольцо `` до комнаты 14
не доходит никогда. Ходить циклом с проверкой номера комнаты.
- **`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
умеет только копирование и XOR потребует ~25 % кадра на CPU — неверно,
поправил пользователь. Заметка описывала копирование, я принял её
неполноту за свойство железа. Проверять по драйверу MAME
(`mame/sources/MAME/src/mame/sinclair/sprinter.cpp`) и `docs/new/06-accel.md`.
- **lldb через FIFO — плохая идея.** Повторяющиеся `-o` при
`breakpoint command add` записываются НЕПОЛНЫМИ. **Работает надёжно:**
`fprintf(stderr, ...)` прямо в SDLPoP, пересборка (секунды) и чтение stdout.
- **`make` без `hdd` не обновляет образ MAME**, а `FIRST_LEVEL` живёт в
`roomtest.c` — при смене нужен `touch roomtest.c`.
- **Доска отставала от кода на три задачи** (`L3-CHOMP`, `L3-SKEL`, шаги 3/5
`L4-MIRROR` значились несделанными). Перед планированием — сверять
- **Не оценивать железо по своей же 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`). Пригодится
всякий раз, когда нужна таблица по экранной X.
- Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`).