Доски приведены в соответствие с кодом; 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
+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`.
Последний коммит сессии — `8beb66a`.
Уровни 1-4 приняты **smoke-тестами**; полные обходы всех комнат делаются по
готовности ВСЕХ уровней ([политика приёмок](TASKS_CLOSED.md#pass-policy),
решение пользователя 2026-08-11) — отдельных `L3-PASS`/`L4-PASS` больше нет.
`tests-host`: 5 наборов, все проходят (`[geom] 3144`).
`make size-check`: чисто, роста нет.
Уровень 5 **не приносит ни одного нового тайла** (инвентарь `res2005.bin`
сверен: всё уже есть с уровней 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` |
| `72797e1` | инвентаризация ВСЕХ делений по `.asm`, три убраны |
| `8175121` | `scr_x` таблицей на весь диапазон, включая отрицательные |
| `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` с разбором |
| `a823e7e` | libbgi: блочные AND/OR/XOR акселератора (строками и колонками) |
| `860468f` | libbgi: NOT_PUT тоже через акселератор + `docs/shadow_render.md` |
| `c0075c7` | NEXT_SESSION: op-блиты сделаны, вид тени отложен |
## 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`).
Пересобрать образ: `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. Состояние окружения
## 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 поднят полный набор блочных
операций акселератора — 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`.
## 4. Что сделано в libbgi и чем это полезно дальше
**Вид тени (два блиттера OR+XOR) отложен решением пользователя** до того,
как будут сделаны все уровни: пока тень рисуется обычной копией из атласов
Кида. Причина не в примитивах — XOR несовместим с нашей прозрачностью
`#FF`, а операция читает ОЗУ-копию экрана, из-за чего два прохода
оригинала вырождаются в один XOR. Всё выясненное, замеры и четыре
варианта — [`../docs/shadow_render.md`](../docs/shadow_render.md). Ключ к
выбору варианта — список кадров, которыми тень реально пользуется
(ожидание: бег, длинный прыжок из зеркала, питьё зелья, боёвка).
Блочные операции акселератора — полный набор 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).
**Что установлено (замером, не гипотезой).** Тень уровня 4 в оригинале
рисуется ДВУМЯ блитами одного и того же спрайта Кида (seg008:1602):
## 5. Остальное открытое
```c
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);
```
OR на месте, XOR со сдвигом на пиксель вправо — XOR гасит совпавшее, остаются
края, отсюда «контурный» вид. Это ЗАМЫСЕЛ оригинала, не артефакт SDLPoP.
Подтверждено печатью из живого SDLPoP (`DBGMIRROR`): и тень, и отражение идут
из `chtab=2` (собственные спрайты Кида), `swordbits=0`, обычными кадрами:
```
type=4 chtab=2 img=40 dir=0 clipL=137 clipT=3 charid=0 frame=41 <- отражение
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).
- **[FORE-DUP](BUGS_OPEN.md#fore-dup)** — передний слой тайла рисуется дважды
при перекрытии объектов (Кид+отражение всегда, Кид+соперник — весь ближний
бой). Картинку не портит, тратит такты. **Сначала замерить, потом
чинить** — окно клипа в своё время дало 3.2×.
- **[MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale)** — зеркало, поставленное
пока игрок В комнате 4, не попадает в снимок `room_fg` и потому невидимо
для коллизии. В обычном прохождении недостижимо; фикс на несколько строк,
делать вместе со следующей правкой `pop_trob.c`.
- **[DIED-ON-BUTTON](BUGS_OPEN.md#died-on-button)** — `died_on_button`
(seg007:776) не портирован.
- **TORCH-ANIM-RIGHT** — под запечённым пламенем могут застыть пики/меч/зелье
справа от факела; на уровнях 1-4 такого соседства нет.
- **[GUARD-PHYS](TASKS_OPEN.md#guard-phys)** — остаток: ветка тени в
`check_guard_fallout` (уходит в L5-SHADOW) и живая проверка стража,
нажимающего напольную кнопку.
- Долгие: `BUG-SPIKE-1`, `BUG-CHOMP-JUMP-1` (оба низкий приоритет),
`L3-PASS`, `L3-COLOR`, `L1-SPEED`, `TUNE-1`.
- **Не проверено в MAME** из вчерашнего: отражение с fore-проходом и клип
тени слева (собрано и залито, но живьём не смотрели).
`L3-COLOR`, `L1-SPEED` (игра на ~39 % быстрее оригинала), `TUNE-1`.
## 5. Грабли, собранные сегодня
## 6. Грабли, собранные 2026-08-10/11
- **Не оценивать железо по своей же memory-заметке.** Я заявил, что accel
- **Не оценивать железо по своей же 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` записываются НЕПОЛНЫМИ (берётся последний), а
оставшийся от неудачной попытки `script print(... lldb.frame ...)` уронил
lldb прямо в обработчике точки останова. Три прыжка пользователя ушли
впустую. **Работает надёжно:** добавить `fprintf(stderr, ...)` прямо в
SDLPoP, пересобрать (он собирается за секунды) и читать stdout. В дереве
уже есть такие метки (`DBG kidobj`).
`breakpoint command add` записываются НЕПОЛНЫМИ. **Работает надёжно:**
`fprintf(stderr, ...)` прямо в SDLPoP, пересборка (секунды) и чтение stdout.
- **`make` без `hdd` не обновляет образ MAME**, а `FIRST_LEVEL` живёт в
`roomtest.c` — при смене нужен `touch roomtest.c`. Я дважды сказал
«образ пересобран», когда он не был.
- Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`: `dx` кадров
Кида −5..+10, стража −2..+10, плюс `render_dx ∈ {140,0,+140}`).
Пригодится всякий раз, когда нужна таблица по экранной X.
`roomtest.c` — при смене нужен `touch roomtest.c`.
- **Доска отставала от кода на три задачи** (`L3-CHOMP`, `L3-SKEL`, шаги 3/5
`L4-MIRROR` значились несделанными). Перед планированием — сверять
`TASKS_OPEN.md` с кодом грепом, а не верить записи.
- Диапазон `obj_x` = **416..695** (посчитан из `kid_data.bin`). Пригодится
всякий раз, когда нужна таблица по экранной X.