L1-START. Старт и оба рестарта (смерть, выпадение из уровня) сведены в
pop_start_level() — порт start_level + do_startpos + set_start_pos (seg003).
Комната/тайл/направление берутся из pop_level_start_*, направление
инвертируется (~start_dir), поза входа — из tbl_entry_pose: у уровня 1 это
падение внутрь (seq_7_fall) плюс нажатие кнопки room5(0,2), то самое, что
захлопывает решётку за спиной. Жёсткие START_ROOM/COL/ROW убраны.
Проверено в MAME: старт даёт room 1, col 0, падение на row 1 — как по данным.
L1-EXIT. Ветка двери уровня из up_pressed + go_up_leveldoor (seg005:0482/
0574): тайлы и геометрия — pop_leveldoor_enter() в pop_map, последовательность
seq_70 — в pop_ctrl. Опкод 0xF1 END_LEVEL в play_seq инкрементит
pop_next_level (порт next_level), главный цикл по нему перезапускает уровень
— ровно та точка, куда levels_plan §2.2 подключит загрузку уровня 2.
Открытость двери проверяется по modifier >= 42 (ветка fix_exit_door), а не по
ванильному leveldoor_open: иначе можно войти в ещё ползущую створку.
Отдельно стоило разбора: go_up_leveldoor сначала писал Char.x/Char.direction,
и оба присваивания молча терялись — окно Char вокруг диспетчера возвращает
назад только curr_seq и sword (pop_savekid_state). Направление оставалось
«вправо», а все DX в seq_70 отрицательные, поэтому Кид уходил ИЗ проёма
влево (поймано стоп-кадром). Геометрию персонажа в этом порте меняет
pop_map, пишет в Kid — как pop_down_action и pop_jump_up_seq.
Известный остаток — BUG-DOOR-CLIP в bug_list.md: нет обрезки силуэта правым
косяком проёма (недопортирован obj_clip_right в clip_char); нужен вариант
колоночного блита с ограничением ширины. ROOMNAV пока оставлен включённым —
он нужен, чтобы попадать в комнату 9 для этой работы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kid_heal/pop_guard_heal звали gfx_heal всегда, хотя рисуют ровно тот
прямоугольник, который блит в большинстве кадров кладёт noclip-ядром.
Все три места heal (+ heal_off фона) сведены к общему pop_heal_fast.
Замер в MAME, счётчики totalcycles на входах kid_heal и kid_tick (вся
группа heal за кадр), комната 1, Кид стоит:
клипающее ядро 26 200 тактов/кадр (149 кадров)
noclip 15 848 тактов/кадр (239 кадров)
−10 352 такта, −39.5 %. A/B в одном прогоне: вторая половина снята с
пропатченным в памяти условием (jr nz → jr), то есть на той же геометрии.
Размер СУММАРНО −362 Б: _CODE +17, BANK2 −116 (свободно 2708 — это тесный
банк из рисков levels_plan §5), BANK3 −263, BANK4 без изменений.
Грабли по дороге: первым заходом хелпер был static inline в pop_bg.h —
SDCC 4.5 И встраивает тело (181 Б) в каждый вызов, И оставляет копию в
каждом TU, который видит заголовок. pop_guard_heal раздулся с ~60 до
663 Б, итого +1091 Б в _CODE и +636 Б в банке стража. Отсюда pop_draw.c:
обычная функция в резиденте W1, из банков это прямой call без трамплина.
pop_room_clip_borders оставлен клипающим осознанно (320 не лезет в 8 бит,
гейт border_dirty редкий) — причина записана в коде.
Проверено визуально: ходьба, прыжок, спуск, позиция за решёткой шва
(straddle — там работает клипающий фолбэк) — артефактов нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три Critical'а (BUG-1 провал на row 1 при боковом переходе, BUG-2 ping-pong
при возврате, BUG-3 окклюзия climb-up на кнопке) висели непроверенными с
2026-07-21. Прогнал в MAME:
- BUG-1 не воспроизводится: room6 → кнопка (0,2) → открытая решётка →
переход влево даёт room8, y=55, curr_row=0. Заодно снят и сам диагноз
записи — репроекция Y при БОКОВОМ переходе не нужна: goto_other_room
(seg002.c:390) меняет только x, наш check_leave делает то же.
- BUG-2 не воспроизводится: шов room2↔room3, четыре пересечения с
разворотом сразу после входа — комната меняется ровно раз на пересечение.
- BUG-3 закрыт фиксом tile_code_drawn от 2026-07-28 (это дубль уже
записанного «спуск с кнопки»); оговорка про непереснятый подъём — в
bug_closed.md.
bug_list.md теперь только открытое (BUG-CEIL-1/2/3, BUG-OCCL-1, T-1, T-2,
таблица обхода 24 комнат) + индекс с якорями. bug_closed.md — закрытое
вместе с разбором корней (odd-pixel char_x, подстановка тайла кнопки, баг
кодогенератора SDCC), он и есть главная ценность архива.
TASKS.md: кросслинки на открытые баги в шапке, в L1-TRIAGE, L1-PASS и
«Отложено». Указатели в CLAUDE.md/README/room_model_plan/layout_plan_v2
переведены на нужный из двух файлов.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ручная проверка пользователем: стало значительно лучше, но редкие пропуски
стрелок при зажатом Shift всё же ощущаются. Счётчики на 35 нажатиях подряд
потерь не показали, то есть остаточная частота заметно ниже прежних ~15 %.
Задача отложена до финальной полировки программы (решение пользователя) —
для работы клавиатура пригодна.
Записано, где именно осталась дыра, чтобы не начинать с нуля: idle-хук
покрывает простой (~2/3 кадра), а в занятой трети DI-окно одного
accel-прохода доходит до ~650 мкс при допуске FIFO ~300 мкс — пачка байт,
целиком попавшая в такое окно, ещё может потерять байт. Порядок действий
на возврат: вызовы между блитами занятой фазы, замер тем же счётным методом
от 50 нажатий, и только потом рычаги вне нашего кода (Scan Code Set 3 через
BIOS $EA — в MAME непроверяемо; общий m_irq_off_timer в драйвере).
Заодно сняты оговорки «плотный опрос ещё не подтверждён замером» в
kbd_raw.h и libc-reference.md — теперь там штатный рецепт через
gfx_set_idle_hook.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Причина потерь (замеры — applications/PoP/roomtest/TASKS.md, KBD-1): при
зажатом Shift PS/2 обрамляет расширенный код «фиктивным шифтом», нажатие
стрелки становится 5 байтами вместо 2, а импульс запроса прерывания здесь
теряется примерно в 44 % случаев — трёхбайтовый FIFO SIO переполняется, и
байт пропадает ДО чтения порта. Лечится только плотным вычерпыванием: раз
в ~0.5 мс. Столько времени есть даром — при пейсинге «3 растровых кадра на
логический тик» процессор проводит ~42 мс из 60 в gfx_wait_vsync, крутя
опрос луча и больше ничего не делая.
- gfx_set_idle_hook(fn) — что вызывать, пока gfx_wait_vsync ждёт луч.
Состояние в отдельном data-модуле (_gfx_idle_state.c), чтобы не тянуть
сеттер в программы, которые хук не ставят.
- Лучевой цикл зовёт хук в обеих фазах. BC (счётчик таймаута)
сохраняется, косвенный вызов — push адреса возврата + jp (hl), так как
`call (hl)` в Z80 нет; без хука это ret по нулевому указателю, порядка
двух десятков тактов в цикле, который и так сжигает время.
- Путь FPS-делителя не затронут: там ожидание через HALT.
- roomtest вешает на хук kbd_raw_poll.
Проверка в MAME счётчиками (брейкпоинты с { b@ADDR = b@ADDR+1 ; g } на
чтении порта 0x18 и на установке make-бита): 35 нажатий Shift+Home → 35
make, ноль потерь; до фикса было 9 из 10. Боевой сценарий: четыре Shift+→
подряд дали четыре осторожных шага (Kid.x 114 -> 147). _CODE +170 Б,
кадровый бюджет не затронут.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Документы отстали от кода: PORT_PLAN писал «PoC не начат», хотя играется
весь уровень 1, а четыре плана были исполнены целиком.
- PORT_PLAN: таблица статусов по разделам; фазы 0-3 сделаны, 4-6 нет;
риски §8 п.1/п.3 закрыты, п.2 переформулирован под реальный движок
(спрайтовый движок для персонажей не используется, лимит «21 спрайт»
неприменим), п.4 — найдено расхождение таймингов: оригинал считает
логический кадр за 5 тиков при BASE_FPS=60 (83.3 мс, в бою 100 мс), а мы
ждём три vsync (60 мс) — игра идёт примерно на 39 % быстрее эталона.
- levels_plan.md — новый: машинерия перехода между уровнями, второй
тайлсет (palace), потабличные различия и читы SDLPoP, которые окупаются
сразу. Инвентарь тайлов снят прямо с res200N.bin: уровень 2 не требует
ни одного нового ассета и ни одной новой механики.
- roomtest/TASKS.md — новый: доска текущих задач с критериями готовности.
- Удалены как исполненные и перекрытые кодом: clip_char_plan,
double_buffer_plan, loose_floors_plan, size_optimization_plan. Его §8
(замеры скорости отрисовки) не был перекрыт — перенесён в
layout_plan_v2 §9, чтобы не потерять цифры.
- KID_PLAN / gates_spikes_plan — шапки «реализовано, оставлено
справочником»; room_model_plan — «S1 сделан, остальное не срочно».
- docs/README.md стал индексом с отметками актуальности.
- ideas_backlog: зелье переворота экрана — оригинал переворачивает готовый
буфер построчно, спрайты не трогает; по данным уровней тип 4 встречается
только на уровне 9, до него механика не нужна.
- examples/scroll: ссылка на удалённый план вела к неверному факту
«теневая копия одна — общая»; заменено на подтверждённое «у каждой
страницы своя».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>