a3aaa30e53ee896b106799c7e18b4cea59750392
16 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a3aaa30e53 |
SprPoP: тесты смерти от меча (базовое поведение до правок 12/13)
КОД ИГРЫ НЕ МЕНЯЛСЯ. Новый набор t_death — 9 проверок, всего наборов 16. Заведён ПЕРЕД правкой находок 12/13, чтобы поймать деградацию: правка 13 в изоляции уже ломала смерть (мёртвый оставался с ненулевой скоростью падения, проваливался за нижнюю границу, игра уходила на рестарт, не показав тела). Поэтому проверяется ровно то, на что эти правки влияют: * удар не в боевой стойке смертелен независимо от запаса HP; * удар с мечом снимает одно HP, на последнем — убивает; * ПЕРЕЖИВШИЙ удар ставится на пол своего ряда с нулевой скоростью падения (в оригинале это единственная ветка, где координата трогается); * тело после смерти остаётся в своём ряду — и на ровном полу, и у самого обрыва (целевая сцена находки 12; после правки ожидание изменится осознанно). По дороге тест дважды показал не баг движка, а мои ошибки в самой сцене: обвязка выставляет признак «жив» только стражу, а урон применяется не сразу — удар выставляет дельту, и HP меняет отдельный шаг кадра, как в оригинале. Оба раза чинился тест. VANILLA/BUGFIXED: записано, что переключатель уже существует в настройках и зафиксирован в положении VANILLA, второй заводить не нужно. Отмечено главное следствие — наш «ванильный» билд УЖЕ не чистая ваниль (часть ванильных багов пофикшена), поэтому при разделении режимов придётся пройтись по сделанным отступлениям и распределить их; отдельные фиксы (падение сквозь стену) могут быть сделаны и в нынешнем билде. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
90e304f071 |
SprPoP: тесты стены + задача VANILLA/BUGFIXED, находка 24 отложена
КОД ИГРЫ НЕ МЕНЯЛСЯ — правка находки 24 сделана и откачена. ТЕСТЫ. Набор t_wall вырос с одной проверки до трёх: добавлены проверка X на каждом кадре падения (персонаж не должен оказываться внутри кладки) и симметричный прыжок через провал с другой стороны. Ключевое в них — форма ожидания. Тест НЕ требует нуля заходов в кладку, а сторожит их ЧИСЛО: сейчас ровно два случая из четырнадцати стартовых позиций. Это ванильное поведение оригинала, для которого SDLPoP держит отдельное опциональное исправление; больше двух — значит правка сделала нас хуже ванили, меньше — значит фикс кем-то портирован. То есть тест сразу готов обслуживать оба будущих режима. По дороге тест дважды ловил не баг движка, а мою ошибку в самой сцене (старт в пустой клетке; перелёт через площадку считался нарушением). Оба раза чинился тест, а не движок. НАХОДКА 24 ОТЛОЖЕНА. Перезагрузка кадра в in_wall верна по букве оригинала, но эффекта показать не удалось: все 15 наборов host-тестов дают одинаковый результат до и после. При этом правка не бесплатна — маппинг окна и перезагрузка кадра на каждое выталкивание. Платить за недоказанное не стали. НОВАЯ ЗАДАЧА: docs/vanilla_vs_bugfixed.md — поддержка двух поведений, ванильного и с багфиксами. Туда переехали находка 24, три опциональных фикса SDLPoP (скольжение сквозь стену, прыжок над воротами, гобелен) и готовый детектор из t_wall. Открытые вопросы записаны: чем переключать (возможно, объединить с уже существующим VANILLA/ENHANCED), цена рантайм-проверки в горячем пути, что считать умолчанием, как гонять тесты в двух режимах. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
b55d4d11e3 |
SprPoP: аудит — правки 12 и 13 неделимы (проверено на живой машине)
Правка 13 (перенос прижатия к полу в ветки пережитого удара, как в оригинале) была сделана в изоляции и СЛОМАЛА смерть: страж убивает Кида, а вместо тела и паузы идут вспышка, стопкадр и мгновенный выход в заставку. Причина: у нас прижатие к полу работало КОМПЕНСАЦИЕЙ отсутствующей ветки «убит и сброшен с уступа» (находка 12). Без неё мёртвый остаётся с ненулевой fall_y, физика ведёт его вниз, он пересекает нижнюю границу, взводится pop_fell_out — и приложение уходит на рестарт РАНЬШЕ отрисовки, поэтому тела не видно вовсе. Оценка «чистое перемещение двух строк, риск низкий» была неверной. В документе исправлено: риск ВЫСОКИЙ, пока ветка 12 отсутствует; обе находки — одна правка, и порядок внутри неё обратный: сперва добавить seq_81 с экспортом тайловых запросов, убедиться, что смерть на краю отыгрывается ею, и только потом снимать страховку. Правка откачена, дерево вернулось к проверенному состоянию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
a5252b1c60 |
SprPoP: глубокое ревью находок А/Б — исправимость и цена по скорости
КОД НЕ МЕНЯЛСЯ. Разбор одиннадцати находок рангов А и Б: что менять, во что это обойдётся по скорости и памяти, каков риск. СНЯТО ГЛАВНОЕ ПРЕПЯТСТВИЕ. Обоснование двух упрощений (guards.c:1011 — «нужны тайловые запросы от Char, а pop_map умеет только от Kid») УСТАРЕЛО: get_tile_at_char, get_tile_infrontof_char, get_tile_behind_char и distance_to_edge_weight в pop_map.c уже работают от Char, они лишь не выведены в заголовок. Данные тоже на месте — pop_char_set_seq ставит любую из 115 последовательностей, то есть seq_81 и seq_64 доступны без единого нового байта. Три находки упираются не в архитектуру, а в четыре строки объявлений. СКОРОСТЬ. Места классифицированы по частоте вызова: play_seq и ИИ стража — горячие, land/in_wall/bumped/hurt_by_sword — событийные. Из одиннадцати правок две УСКОРЯЮТ код (уходит условие из горячего цикла; звук перестаёт играть в двух случаях из трёх), большинство бесплатны (перестановка строк), и ни одна не требует переделки архитектуры. Единственный конфликт со скоростью — отложенная побудка чомперов: play_seq маппит страницу байткода в W0 один раз перед циклом, и звать start_chompers внутри цикла значило бы снимать и возвращать окно на каждый переход ряда. Дешёвая замена: копить не один флаг, а битовую маску рядов и разбудить их после цикла — теряться ряды перестанут, цена в цикле нулевая. Для стражей аналогично: не межбанковый вызов wall_type, а копия таблицы в 32 байта в своём банке. Порядок работ — от «одна-две строки, низкий риск» (13, 24) к тем, где правка может компенсировать наши отличия в другом месте (1, 7). Политика: для критичных фиксов скорость не вето — такие выносятся в отдельный разбор с поиском дешёвого способа. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
3a0e847353 |
SprPoP: аудит расхождений с SDLPoP — Кид, стражи, seqtbl, отрисовка
КОД НЕ МЕНЯЛСЯ. Построчный разбор наших реализаций против оригинала: 26 позиций за пять проходов, каждая с рангом вероятности (А..Д), с описанием «чем грозит» и сценарием проверки. Расхождения группируются в три узла, и это главный вывод аудита: 1. СМЕРТЬ ПРИ АКТИВНОЙ ФИЗИКЕ — здесь все находки ранга А. У нас смерть это флаг, а физика продолжает вести персонажа как живого: нет ветки «убит и сброшен с уступа» (оригинал выбирает её по тайлу позади), прижатие к полу в hurt_by_sword стало безусловным (в оригинале только для выжившего удара), в land лишний пересчёт колонки. Этим объясняется наблюдение пользователя: заколотый на краю Кид доезжает этажом ниже и садится в присед. 2. ГРАНИЦЫ МОДУЛЕЙ — pop_map не отдаёт наружу тайловые запросы от произвольного Char, wall_type и загрузку кадра. Три ветки упрощены НЕ по логике, а по доступности функций: отсутствующая ветка уступа, «стена впереди» сужена у стражей до одного тайла (оригинал считает преградой ещё ворота, верх двери, зеркало и чомпер), in_wall не перезагружает кадр. Чинить это заплатками неправильно — сначала расширять интерфейс pop_map. 3. МОМЕНТ ПОБОЧНЫХ ДЕЙСТВИЙ — делаем то же самое, но раньше или позже: сброс fall_x, побудка чомперов (у нас отложена до конца play_seq), звук удара, перезагрузка кадра. По отдельности мелочь, вместе — сдвиг состояния на кадр. Восемь позиций СВЕРЕНЫ И СОВПАДАЮТ (диспетчер control, все 15 опкодов seqtbl, control_with_sword, parry, swordfight, sword_strike, check_sword_hurt, check_hurting, bumped_fall, таблицы кадров) — их не нужно перепроверять. Дважды по ходу работы едва не записана ложная находка из-за чтения отфильтрованного вывода; отсюда правило: фиксировать расхождение только после чтения обеих реализаций целиком. Отдельно: второе наблюдение пользователя (падение частично в стене) — у SDLPoP есть ТРИ опциональных фикса ровно про это, то есть в ванили баг присутствует, и мы его намеренно повторяем. Но найдены два места, где мы можем быть хуже ванили (гард curr_row<=2 в do_fall и in_wall выше). Незакрытое перечислено в файле: тела autocontrol_*, check_grab, check_bumped_look_left, старшие биты байта клинка. Также отмечено, что ни одно найденное осознанное отличие не занесено в docs/impl_diff.md, хотя правило проекта этого требует. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
df5071a967 |
SprPoP: музыка без перелинковки — длины и длительности уехали на диск
Часть I плана music_runtime_index_plan.md (MI0..MI5). gen/pop_music_tbl.h и gen/pop_music_ticks.h УДАЛЕНЫ: длины треков и длительности реплик читаются из MUS/mus.idx (формат PMI1, tools/pop_idx.py, тесты в make test-tools). Один и тот же sprpop.exe работает с любым из четырёх наборов записей — sha256 бинарника при смене MUSIC_FMT не меняется. ГДЕ ЖИВЁТ ИНДЕКС. 228 байт таблицы в W2 не положить (свободной кучи там порядка двух сотен), поэтому индекс лежит в одной странице EMM, а в резиденте от него два байта. Данные в странице — со смещения 0x100: gfx_w0_page_prepare пишет в неё стабы прерываний (0x38 и 0x66), и с нуля они попали бы прямо в записи id 10 и 21. Со смещением работает штатная защита, а не запрет прерываний (тот же приём, что CFG_BASE в pop_config.c). Число страниц в индексе не хранится — считается из blocks, чтобы не разъехалось. ПАУЗА КОНЦА УРОВНЯ — СОСТОЯНИЕМ, А НЕ СЧЁТЧИКОМ. pop_endmus_left и POP_MUS_TICKS_32/41 удалены; главный цикл ждёт pop_music_active() — «заявка лежит, идёт загрузка или трек звучит». Одного busy мало: между заявкой и первой нотой 190-230 мс (замер в sound_plan §9). Прежний счётчик закрывал эту щель ценой зависимости EXE от набора и жёсткого делителя /4, который врал в режимах FAST/FASTEST (там логический кадр 3 кадра луча, а не 4). Побочно исправилось расхождение с SDLPoP: при выключенном звуке заявка не кладётся, и уровень меняется сразу, как в оригинале (seg006:651 + seg003:387) — раньше игра держала пройденный уровень лишние 12 секунд в тишине. PV-СЦЕНА — на четырёх якорях (8 байт статики), которые считаются из индекса при входе в сцену; прежние выражения шкалы не изменились. План предлагал протащить структуру времён через пять функций — для сцены, которая идёт раз за запуск, это того не стоит. ПАМЯТЬ. За обе фазы резидент не вырос, а освободился: _CODE 23865 -> 23544, куча 239 -> 256 Б. Банк 9 похудел на 118 Б (ушла pop_mus_tbl из rodata), банк 11 — на длительности реплик. ПРОВЕРЕНО В MAME: exe побайтово одинаков для flac и mt32; все 22 трека в индексах различаются, и контрольные значения совпали с предсказанными планом (m41 732->685, m50 831->867, m53 985->1044, m56 9865->10462 блоков, 78->82 страницы); на mt32 PV-сцена проходит целиком по его длительностям; без mus.idx музыки нет, эффекты работают, игра проходима. НЕ ПРОВЕРЕНО: потоковый m56 на 82 страницах — до финала надо дойти в игре. Единственный оставшийся пункт приёмки, отмечен в sound_plan §11.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
2349481b86 |
SprPoP: звуковые эффекты без перелинковки — раскладка уехала на диск
Часть II плана music_runtime_index_plan.md (SI0..SI4). gen/pop_sound_tbl.h БОЛЬШЕ НЕ ГЕНЕРИРУЕТСЯ: раскладка набора читается из SND/snd.idx (формат PSI1, писатель и разборщик — tools/pop_idx.py, 22 теста в make test-tools). Один и тот же sprpop.exe работает с набором SDLPoP (9 страниц) и MSDOS (10) — sha256 бинарника при смене набора не меняется. Заодно умолчание источника эффектов переведено на SDLPoP (SND_SRC=sdlpop): сборка обязана работать без оригинального дистрибутива DOS. У кого он есть, включает лучший набор явно — make SND_SRC=msdos (там полнее оцифровка: в SDLPoP звук 48 spiked пустой). Устройство: pop_snd_tbl/pop_snd_page/pop_snd_pages — резидентные данные (pop_snd_data.c), тип и инварианты — рукописный pop_snd_tbl.h. Записи читаются ОДНИМ read прямо в таблицу, поэтому sizeof(pop_snd_ent_t) == 5 стало частью дискового контракта: проверяется статически и полем размера записи в заголовке. POP_SND_PAGES как compile-time размер набора исчез — вместо него POP_SND_MAX_PAGES (вместимость, 16) и runtime pop_snd_pages. Цена: таблица переехала из _CODE в _DATA, суммарный резидент почти не изменился (куча 239 -> 229 Б); банк 8 +601 Б на чтение и валидацию. Валидация не доверяет файлу: заголовок целиком плюс каждая запись (страница, смещение, кратность блоку, непересечение с блоком тишины, выход за последнюю страницу). Последнее считается В БЛОКАХ — байтовый адрес конца не влезает в uint16, а 32-битная арифметика на Z80 дорога. НЕТ ИНДЕКСА — ЭФФЕКТОВ НЕТ, НО МУЗЫКА ИГРАЕТ. Первая версия просто возвращала ошибку, и игра становилась непроходимой: тишину льёт первый блок набора, без набора CBL не открывался, а с ним вставала музыка (её блоки считает тот же насос) — заставка ждала конца трека вечно. Теперь поднимается пустой набор с блоком тишины. Заливается ровно 128 байт и под DI: gfx_w0_page_prepare ставит в страницу IRQ-стабы, и заливка всей страницы затирала их — первое же прерывание давало чёрный экран. Грабли сборки: смена SND_SRC тихо давала неверный результат (sdlpop -> msdos -> sdlpop оставлял чужой набор в assets/packed). Причина не в логике, а в секундной гранулярности mtime. Лечение убирает время из решения: смена варианта сносит stamp'ы своего семейства, а упаковка, сборка архива и копия индекса делаются одним рецептом. То же получила и музыка (MUSIC_FMT). Проверено в MAME: таблица в памяти совпадает с файлом из образа побайтово; один EXE поднимает оба набора; отладочный --order reverse (30 из 31 записей отличаются от штатных) звучит правильно; битый индекс выключает эффекты, не роняя игру; без индекса PV-сцена проходит с музыкой; Ctrl+S работает в обоих режимах. Разбор — docs/sound_plan.md §10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
ea8efdb0fd |
SprPoP: обобщить HDD-сборку и очистить метаданные
Добавить общий каталог назначения для HDD и удалить локальную копию упаковщика.\n\nУбрать устаревшие generated-имена ресурсов, выводить число страниц Kid из kid.arc и ограничить звуковую таблицу горячим модулем.\n\nЗафиксировать планы runtime-индексов музыки и PCM-эффектов. |
||
|
|
4ac3584bf9 |
SprPoP: идея «готовить следующий уровень под мелодию» — в бэклог, на дальнюю версию
Записана с оговорками, найденными при сегодняшнем разборе: загрузку придётся разрезать на дисковую и палитро-экранную половины, шаг подкачки держать полустраничным, проверить EMM-бюджет на два уровня разом. Половина идеи уже работает — трек заставки играет поверх загрузки (порядок оригинала, замер насоса приложен в записи). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
770b946a36 |
SprPoP: доски — SND-PACE-DEAD снят, PV-RENDER-BOUND исправлен
Обе записи закрыты сегодняшними правками: вторая шкала по насосу удалена вместе с гонкой, которая её выбирала, а «сцена дороже бюджета» оказалась не ценой кадра, а местом отсчёта интервала. Исходные разборы оставлены под заголовками — они объясняют, как искали. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6dabe9b4b1 |
libc: пока raw-клавиатура открыта, не звать обработчик DSS — он крал наши скан-коды
Корень двух багов SprPoP (KBD-STUCK-WAIT, частично GRAB-KBD-TIMING) нашёлся в исходниках DSS (docs/sources/Estex-DSS): обработчик прерывания DSS живёт в IM1 по 0x0038 и ПЕРВЫМ ДЕЛОМ делает `CALL KEYSCAN`, а тот вычерпывает FIFO SIO досуха. Наш трамплин проверял «есть ли клавиатурный байт» один раз, на входе в прерывание, а хвост кадрового пути уходил в DSS — значит скан-код, прилетевший позже, доставался DSS и уезжал в его буфер. Rx-overrun при этом НЕ взводится (байт не потерян железом, а прочитан не тем владельцем) — отсюда и загадка исходного диагноза: бит залип при `_kbdraw_overrun == 0`. Измерено в MAME (брейки + totalcycles): окно 738 тактов (~34 мкс) на каждом кадровом прерывании, из них 481 такт — пролог самого DSS. Поэтому проверка FIFO перед chain'ом снимает лишь треть и не годится (пробовали, кражи продолжались); кадровый путь при открытом raw теперь заканчивается приватным RETI, окно = 0. Цена: на это время у DSS замирает опрос мыши и мигание текстового курсора — зафиксировано в <kbd_raw.h>. Пойманный случай (старая сборка): DSS прочитал 0x74 (make стрелки «вправо») при _kbdraw_pending = EXT, то есть посылку E0 74 разорвало пополам между двумя владельцами канала. Проверка: брейк на входе KEYSCAN с условием «страница точно DSS + raw открыт» до правки срабатывал мгновенно (50/с), после — молчит; положительный контроль на нашем RETI срабатывает сразу. Трамплин 300 -> 310 Б (буфер W2-копии поднят 336 -> 384, запас 74 Б); размерный эталон обновлён: +10 Б у программ, линкующих IRQ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4e43890fce |
SprPoP: финал больше не убивает программу — прямой вызов в чужой банк
Пройденная игра доходила до таблицы рекордов и умирала: программа
исчезала, машина следом вставала намертво (di;halt на 0x0000) либо уходила
в reset. Одинаково из Flex Navigator и из голого DSS.
КОРЕНЬ. pop_ui.h объявлял группу pop_text_*_mapped БЕЗ __banked. Пока
pop_hof.c лежал в банке 9 рядом с pop_ui.c, прямой call был верен; после
переноса pop_hof/pop_config/pop_pal в банк 10 тот же call стал уходить в
пустой хвост чужого банка. Процессор полз по 0xFF до 0x0000, где ловушка
DSS ставит B=0x27 и сворачивает процесс — подмена страниц W1/W2/W3,
которую было видно на трупе, оказалась уборкой, а не причиной.
Точную инструкцию (call $E503 = _pop_text_map банка 9) дала трассировка
MAME на узком участке: trace включалась брейкпоинтом на входе в
pop_hof_show и выключалась на процедуре завершения процесса DSS (0x1E56).
ЧТО СДЕЛАНО
* pop_ui.h/.c — группа text_*_mapped помечена __banked.
* toolchain/check_bank_calls.py — две проверки банкового кода:
1) прямой call в чужой банк (доказательна, ВАЛИТ сборку — проверено
намеренной поломкой);
2) указатель на данные своего банка, отданный в чужой (эвристика по
форме кода, только предупреждает).
Встроена в app.mk, запускается сразу после линковки.
* pop_hof.c — курсор ввода строится на стеке: литерал "_" лежал в _BANK10
и после пометки __banked уезжал из-под ног чужому банку, заливая экран
знаками вопроса.
* libc: kbd_raw_keypad_as_ext() — kbd_raw_sync переносит голые коды
нумпада в EXT-половину карты. Лечит залипание стрелок (потерянный
префикс E0 сажал make в PLAIN как код нумпада, и снять его было нечем),
заодно нумпад стал управлением: 7/8/9, 4/6, 2 и 5 = вниз.
* pop_pace.c — цикл ожидания луча зовёт тот же idle-хук, что и
gfx_wait_vsync: без этого F10 в геймплее не работал вовсе.
* pop_hof.c — Esc в таблице рекордов отменяет запись (расхождение с
оригиналом записано в docs/impl_diff.md).
* Экран версии показывается только через Menu/Settings/About: стартовый
показ и Ctrl+V убраны, мёртвый код снят.
* sprpop_cold.c — pop_start_level зовёт pop_hp_invalidate: после Ctrl+A с
выросшим за уровень максимумом полоса HP моргала между страницами.
Разбор всех четырёх багов — в applications/PoP/roomtest/BUGS_CLOSED.md
(FINAL-BANKCALL, FINAL-HOF-GARBAGE, KBD-ARROW-PHANTOM, F10-GAMEPLAY),
правило про банки — в applications/SprPoP/CLAUDE.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
659071838d |
SprPoP: KEYS-D, KEYS-R и KEYS-F12 — на доску отложенного
Три остатка плана keys_plan.md записаны в TASKS_OPEN.md с полным разбором, чтобы не выводить его заново. KEYS-D (осмотр соседних комнат) — главное, что стоит помнить: у SDLPoP это три строки, потому что там drawn_room влияет ТОЛЬКО на отрисовку, а физика ходит через get_tile(room, col, row) с явной комнатой. У нас наоборот — карта коллизий грузится для ОТРИСОВАННОЙ комнаты: room_fg, lcol_fg, rcol_fg, above_fg, below_fg это ОДИН комплект на программу. Уведи cur_room к соседу, не трогая kid_room, и Кид считает столкновения по чужим тайлам. Задел под расхождение уже стоит (kid_room отдельной переменной, update_kid_render_dx со сдвигом ±140), но enter_room_side пишет обе разом — это незакрытая часть S3 straddle. Записаны оба пути с ценой: честный (правка ядра, дни) и смотровой режим с остановкой игры (150-250 байт, часы) — плюс что главный риск не в отрисовке, а в возврате. KEYS-R (воскрешение) — четыре места, которые обязаны знать про окно неуязвимости, со ссылками на seg-код. KEYS-F12 (скриншот) — «возможно, когда-то», по пометке пользователя. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
76f02e76db |
SprPoP: раскладка управления — фазы A и B плана keys_plan.md
Приводим клавиши к keys.txt (раскладка SDLPoP). Работа делится надвое, и
здесь только то, что не трогает игровую логику: переназначения (A) и три
мелкие функции поверх готовых механизмов (B). Читы, требующие правки
логики, и осмотр соседних комнат — фазы C и D, отложены.
ПЕРЕНАЗНАЧЕНИЯ (A)
U -> Shift+I переворот экрана; U отдан «комнате сверху»
Esc -> Backspace меню; Esc остаётся дублёром, как у SDLPoP
F7 / F8 -> - / + ±минута, основной ряд и цифровой блок
- / + -> Ctrl+- / Ctrl++ обход комнат (наш отладочный телепорт)
I -> Ctrl+I бессмертие
P -> Ctrl+P режим скорости
1 и 2 -> Ctrl+F стоп-кадр, теперь одной клавишей
F10 в игре -> Ctrl+Q вторая клавиша выхода; F10 ловится глобально
Плюс новое на готовых путях: Ctrl+A — рестарт уровня, Ctrl+V — версия
сборки (функция была, её показывал только старт), Ctrl+D — отладочная
строка (тот же тумблер, что в Settings; POP.CFG не пишем), Home / Page Up —
дублёры диагональных прыжков (у SDLPoP это не отдельное действие, а те же
Up+Left / Up+Right, поэтому просто добавляются к стрелкам).
Все наши сверхштатные клавиши ушли под Ctrl, чтобы не занимать голые буквы
из раскладки, и вписаны в keys.txt отдельным разделом. Ctrl+B намеренно
не занята: keys.txt держит её под «вернуться в комнату Кида» (фаза D).
ФУНКЦИИ (B)
Space «сколько осталось» (seg000:612). Не печатает сама: поднимает тот
же pop_show_time, которым пользуется автоматическое объявление
минут, и строку собирает time_msg() — «59 MINUTES LEFT» и «11
SECONDS LEFT» остаются в одном месте.
T постоянный показ таймера. Переиспользует поле DBG_F_TIME
отладочной строки, своего рендера нет. У верхней полосы теперь
три состояния, и отслеживается РЕЖИМ (0 нет / 1 таймер / 2 всё),
а не флаг: переход «таймер -> полоса» тоже перерисовывает всё.
Ctrl+R возврат в заставку — тот же переход, что «Restart Game» в меню.
ДВЕ ЛОВУШКИ, найденные по дороге
Модификатор обязан входить в САМО значение, а не в условие блока: с
`if (ctrl) { nav = ...; nav_prev = nav; }` при отпускании Ctrl кромка
застревала ненулевой и следующее нажатие глохло.
Один скан-код на два чита: Shift+I и Ctrl+I — это 0x43 в обоих случаях.
По той же причине ±минута требует ОТПУЩЕННОГО Ctrl (иначе сработает и
время, и обход комнат), а T — отпущенных Shift и Ctrl (Shift+T отдан
«добавить HP» в фазе C).
Обработчики положены в pop_frame_ui (банк 8), а не в резидент: там куча
всего 308 байт. Проверено в MAME: Ctrl+D поднимает строку
«Level 1, Room 1, Speed: NORMAL...», T — один таймер 59:30 без подписей,
Space — watchpoint на pop_show_time ловит запись значения 2 (именно
обработчик клавиши, автообъявление пишет 1).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
808c2a5349 |
SprPoP: заставку можно прервать в любой её точке, F10 — выход
Три места, где нажатие раньше не работало или работало наполовину. FADE. fade_run() был замкнутым циклом на 2,13 с без опроса клавиш, а на стыке экранов их два: пропуск работал внутри сценария, но не между его шагами, и заставка ощущалась невыключаемой. Добавлены прерываемые варианты (pop_ui_fade_*_skip, обёртки в pop_pal) — отдельными функциями, а не флагом в прежних: в меню паузы и на переходе уровня прерывать нечего, и менять там поведение молча не следует. Прерванный fade всё равно доводит палитру до конца, экран не остаётся на промежуточной ступени. Подключено в интерпретаторе сценария, сцене с принцессой, четырёх катсценах cut_*, титрах и таблице рекордов. ПРОЯВЛЕНИЕ ПОЛОСАМИ. pop_screen_present_ltr() была void и нажатие ГЛОТАЛА: полосы схлопывались, картинка появлялась целиком — и всё, вызывающий о нажатии не узнавал. На заставке это выглядело как «клавиша срабатывает наполовину». Теперь возвращает признак прерывания, и он проброшен по маршруту: первый экран истории, титры финала, логотип между «свадьбой» и титрами. Везде считается КРОМКА нажатия от входа, как в сценах: клавиша, которой закончили предыдущий экран, ещё зажата, и принимать её за новое нажатие нельзя — иначе весь маршрут заставки схлопывался бы сам собой. F10 — НЕМЕДЛЕННЫЙ ВЫХОД, откуда угодно. Проверка стоит в kbd_idle(), а этот хук висит на gfx_wait_vsync, то есть вызывается везде, где программа ждёт кадр: заставка, титры, fade, проявление полосами, меню, игра. Одна точка вместо десятка по циклам ожидания. Флаг pop_quit_req резидентный — взводится и читается без трамплина из любого банка. Проверка в НАЧАЛЕ витка автомата обязательна: заставку прерывает любая клавиша, и без неё F10 успевал уронить программу в загрузку уровня перед закрытием. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
31b82661eb |
SprPoP: автономное приложение, выделенное из roomtest
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |