aaa480d0f2c45ad3cafda9e2d7db73c467bb7b67
29 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8548aa9132 |
SprPoP: кнопка под Кидом больше не заливается кладкой (палас)
Симптом (пользователь, 2026-09-02, уровень 4 комната 18): Кид подтягивается на открывающую кнопку (0,4), и кромка, которая его прячет, нарисована кирпичом вместо кнопки. РАЗБОР ПО ЖИВОМУ КАДРУ (заморозка): Kid frame 139, x 123, col 4, row 1, room 18 — то есть персонаж действительно под кромкой кнопки (0,4). Отсюда путь: кадр подъёма -> climb_overlay_tile; тайл не floor-подобный (кнопка) -> other_overlay_tile; сосед слева (0,3) пуст -> overlay_mid_tile, полная перерисовка тайла ПОВЕРХ персонажа. ПРИЧИНА. draw_tile_base оригинала подменяет базу кнопки на «левую половину без пола слева» (id 148) по ТРЁМ условиям (seg008:628): тайл — opener, слева пусто И tbl_level_type[current_level] == 0, то есть только в ПОДЗЕМЕЛЬЕ. Третье условие у нас было потеряно ИМЕННО В ОВЕРЛЕЕ: статическая отрисовка (pop_room.c, draw_tile_base) его имеет и даже ссылается на ту же строку оригинала, а pop_bg.c — нет. Уровень 4 дворцовый, поэтому оверлей брал спрайт 148, а в дворцовом наборе это другая картинка. Этим же объясняется, почему баг не виден «просто так»: пока тайл рисует статика, кнопка правильная — кирпич появляется ровно в тот момент, когда персонаж встаёт под кромку и включается оверлей. Цена: +6 байт в банке 2 (8976 -> 8982, свободно 7402). Проверять стоит на любом ДВОРЦОВОМ уровне, где у открывающей кнопки слева пусто и персонаж лезет на неё снизу. Живая проверка в MAME пользователем: корректно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1 |
||
|
|
cb995bf0fc |
SprPoP: непрерывный взмах клинка у безоружного Кида
Симптом (пользователь, 2026-09-02): на подъёме Кида шёл нескончаемый свист клинка, хотя меча у него нет. ОПОЗНАНИЕ. Звук снят с живой машины, а не угадан: snd_curr == 11 (sound_11_sword_moving). Заодно проверено, что тракт исправен — указатели насоса (pg 2, ptr 0x2B80, left 512) сошлись с записью 11 в SND/snd.idx (страница 2, off 0x2880, длина 1280, конец 0x2D80) байт в байт. То есть играл честно заявленный эффект из своих данных, и виновата была ЗАЯВКА, а не звук. ПРИЧИНА. Хвост check_hurting был портирован не до конца. В оригинале (seg002:1039..1044) звук 11 стоит В КОНЦЕ функции, после ОБЕИХ веток, и защищён тремя вещами: ранним выходом по dir_56_none, кадром 154 и условием Opp.action != actions_99_hurt. У нас он стоял ВНУТРИ ветки «не парировано», до присвоения Opp.action = 99_hurt, без проверки на попадание и — главное — без гарда по dir_56_none, который автор SDLPoP подписал прямым текстом: «Fix looping sword moving sound». Направление dir_56_none означает «персонаж выключен» (clear_char), махать ему нечем. Наш движок эту константу знает и применяет в pop_guard_tick — в check_hurting она просто не доехала. ТОНКОСТЬ ПОРТА: хвост оригинала ПЕРЕЧИТЫВАЕТ Char.frame и Opp.frame у персонажей, а не берёт снимки начала функции. Ветка парирования только что записала Opp.frame, а play_seq в ней — Char.frame; на кэшированных cf/of условие дало бы неверный ответ. Живая проверка в MAME пользователем: корректно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1 |
||
|
|
38fb3c03bb |
SprPoP: зацеп больше не срывается на кадре после захвата
Регресс от
|
||
|
|
fee3bdb354 |
SprPoP: ALLOCS по умолчанию 10000; обёртка dist_to_edge — за своим хелпером
Правки пользователя, разобранные по диффу и закоммиченные как есть. Makefile: умолчание --max-allocs поднято с 6000 до 10000 — плотнее упаковка регистров. Напоминание из шапки остаётся в силе: занятость банков сравнима только при ОДНОМ значении ALLOCS, иначе сравниваются не правки, а уровни оптимизации. pop_map.c: банковая обёртка pop_dist_to_edge_weight() переехала НИЖЕ статического distance_to_edge_weight(), который она зовёт, — раньше стояла до его определения. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4 |
||
|
|
8490288d79 |
libc/crt0: возврат из main завершает программу по-настоящему
Два бага одного пути завершения, оба видны только на железе.
1. ЗВУК ПРОДОЛЖАЛСЯ ПОСЛЕ ВЫХОДА. cbl_close() закрывает СЕССИЮ, но не
гасит железо: bit7 порта 0x004E держит gfx_wait_vsync ради бита луча,
поэтому порт оставался включённым ("немой" режим), и CBL крутил свои 256
слотов уже под шеллом — тихо ровно до первой чужой записи в порт данных,
а дальше она зацикливалась. Новый cbl_shutdown() гасит bit7 независимо
от держателей и центрует ЦАП обычного COVOX; pop_shutdown зовёт его
последним действием, а _cbl_open_raw регистрирует в atexit его, а не
cbl_close.
2. ЦЕПОЧКА atexit НЕ ВЫПОЛНЯЛАСЬ ПРИ ВОЗВРАТЕ ИЗ main. crt0 уходил прямо
в ESTEX EXIT, то есть нарушал контракт C (возврат из main = exit(status)).
Молча терялись не только гашение звука и снятие vsync-ссылки, но и
_fclosall: буферизованная запись в файлы пропадала, если программа не
звала exit() явно. Теперь crt0 после main дёргает _atexit_hook.
Косвенность обязательна: прямая ссылка crt0 на разматыватель притащила бы
его и стек хендлеров в КАЖДУЮ программу. Указатель живёт в отдельном
data-модуле (два байта _DATA, ни байта кода), ставит его сам atexit() при
первой регистрации — нет регистраций, нет и кода. Тот же приём, что у
_irq_cbl_hook.
Цена замерена: +14 Б всем программам (блок в crt0) и +64 Б тем
одиннадцати, что реально регистрируют хендлеры (CBL, файловые через
_fclosall, irqtest, gfx_dbuf, solidt) — у них раньше эти хендлеры были
мёртвым кодом. Эталоны обновлены (кроме atlas: его +434 Б не отсюда,
замерен тот же и без этих правок).
Проверено в MAME: старт и звук как были, выход по F10 возвращает в шелл
чисто (текстовый режим восстановлен, зависания нет), повторный запуск
работает. Пункт 1 проверяется только на железе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
|
||
|
|
6176f6da31 |
SprPoP: Restart Game возвращал заставку без музыки и вешал её на титуле
pop_boot_sound() был целиком под разовым флагом, а pop_sfx_start() стоял внутри него. До перестановки старта CBL открывал сам шаг 0 титров, и делал это КАЖДЫЙ раз; теперь второй вызов уходил по раннему return, а pop_title_show начинается с pop_sfx_pause() — значит после Restart Game насос оставался выключенным и заставка шла молча. Тем же корнем объясняется зависание: с закрытым CBL курсор трека не двигается, pop_mus_left не убывает, и ожидания «дослушать тему» в титрах и интро (while (pop_music_busy())) висели до нажатия клавиши — на экране Prince of Persia. - pop_boot_sound: под разовым флагом осталась только ЗАГРУЗКА (индекс звука, первая страница набора, индекс музыки, settings_apply); pop_sfx_start() зовётся всегда — он идемпотентен и уважает Ctrl+S; - pop_music_busy(): признак теперь «звучит», а не «есть курсор» — спрашивает и про открытый вывод (pop_snd_ok). Ждать неиграющую музыку нельзя в принципе, и эта строка закрывает весь класс подвисаний. Проверено в MAME: уровень 1 -> ESC -> RESTART GAME -> титул с музыкой (pop_snd_ok=1, курсор трека прошёл страницы 8..16, pop_mus_id=54), дальше последовательность сама уходит в сцену с принцессой. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4 |
||
|
|
7377bf6a2c |
SprPoP: заставка и музыка сразу после запуска, а не через несколько секунд
От запуска до проявления титула проходило ~3 с чёрного экрана в тишине: pop_boot читал ВСЕ ресурсы игры до первого пикселя, а pop_title_show перед fade_in вычитывал целиком трек заставки (248 КБ). Старт разбит на три фазы (sprpop_cold.c): - pop_boot() — настройки, POP.CFG, графика, чёрный экран, FONT.ATL; - pop_boot_sound() — snd.idx + ОДНА страница snd.arc, индекс музыки, CBL; - pop_boot_rest() — остальные страницы звука, kid.ani, Тень, атласы Кида; идемпотентно, между кусками доливает трек. pop_title_show теперь: fade_in -> pop_boot_sound -> pop_music_stream(54) -> pop_boot_rest под стоящим титулом. Порядок «сначала fade_in, потом тема» сохранён как у show_title (seg000.c:1981). Первый такт заставки укорочен на фактически потраченное время (часы насоса 85,4 Гц -> кадры луча сдвигами), иначе сцена уехала бы относительно музыки. title_wait доливает трек по полстраницы за кадр. Музыка играет с ПЕРВОЙ страницы: pop_music_stream уже был (игровые джинглы), кольцо won не понадобилось — m54 это 16 страниц из 20 доступных. Звук поднимается в два приёма (pop_sfx_init_begin/finish): насосу для тишины нужен ровно один блок, и упаковщик обязан класть его первым блоком страницы 0 (tools/pop_idx.py) — значит для открытия CBL хватает ОДНОЙ страницы (33 мс вместо 440). Пока набор неполон, pop_sfx_play пропускает эффекты; заявки на музыку проходят. Четыре загрузки из boot УБРАНЫ, а не отложены: атлас фона, страж, уровень 1 и pop_pal_game_load — их и так делает pop_level_switch на входе в любой уровень (а pop_new_game_load ещё и палитру перед ним), и оба маршрута в игру, LEVEL_LOAD и DEMO, упираются в него. Это ~0,77 с на ровном месте. Так же устроен оригинал: init_game_main до заставки читает только меч, пламя, звуки и палитры, а chtab_5/6/7 грузит load_lev_spr на старте уровня. Страховка на случай пропуска заставки в первую секунду — идемпотентный pop_boot_rest() в начале pop_new_game_load и pop_level_switch. FONT.ATL остаётся в минимальной фазе, и это не про шрифт: снимок палитры физически лежит в хвосте его страницы (pop_ui.c), а вся машинерия яркости начинается с `if (!font_ready) return`. С отложенным шрифтом заставка возникала разом на полной яркости — поймано пользователем. Проверено в MAME: титул виден сразу и проявляется полосами, pop_mus_left убывает (трек стримится), pop_snd_pages=10 (набор дочитался), цепочка титул -> интро -> пропуск -> уровень 1 работает. Банк 8: 95,5 % (732 Б). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4 |
||
|
|
63bfd997a9 |
SprPoP: звук мигания, равномерные часы, полоса HP после рестарта, надпись ждёт мелодию
Четыре правки прогона 2026-08-31. Две ПРОВЕРЕНЫ пользователем в MAME (звук мигания, порядок «мелодия -> надпись»), две ждут проверки — образ собран. ЗВУК МИГАНИЯ (проверено). Упаковщик научился синтезировать ноты PC- спикера в обычный сэмпл: заголовок с темпом, тройки «частота + длина», меандр на нашей частоте вывода. Берём из нот ТОЛЬКО номера, которых нет ни в оцифровке, ни среди мелодий, — иначе синтез перекрыл бы музыку, которую мы играем из MUS/. На поставке SDLPoP это ровно один номер: 38, сигнал под мигание «Press Button»; 31, 34 и 42 там пустые заглушки. Громкость по слуховой проверке снижена вдвое (44 -> 22): на полном размахе сигнал перекрикивал игру. EXE не меняется — раскладка читается с диска, набор занял те же 9 страниц. НАДПИСЬ ЖДЁТ МЕЛОДИЮ (проверено). Порядок оригинала: ветка мёртвого (seg006:1351) на седьмом шаге выходит, пока звук играет, и «Press Button» появляется только после музыки смерти. Чтобы ожидание не было принудительным, три быстрых пути (Ctrl+A, обе быстрые загрузки, пункты меню) музыку глушат — оригинал при Ctrl+A делает то же (seg000:0617). Обычная кнопка во время мелодии не действует: она ответ НА надпись. ЧАСЫ (ждёт проверки). Тик стоит столько кадров ЛУЧА, сколько их в кадре режима NORMAL, поэтому FAST/FASTEST больше не ускоряют время. Считаем ФАКТИЧЕСКИ прошедшие кадры луча, а не ожидаемый делитель: логический кадр не всегда укладывается в бюджет, и часы «по делителю» шли рывками (первый прогон это показал — «несколько секунд быстро, потом притормаживание»). Вклад одного вызова ограничен, иначе пауза и меню прыгнули бы вперёд. Четыре новых теста: NORMAL не сдвинулся ни на тик (и вне боя, и в бою), FAST и FASTEST держат реальное время. ПОЛОСА HP ПОСЛЕ Ctrl+A (ждёт проверки). Корень: счётчик считает СТРАНИЦЫ, а тратился по КАДРАМ — между двумя вызовами переворота может не быть, и оба прохода уходили в одну страницу, вторая оставалась с делениями прошлого боя. Теперь проход тратится только при смене gfx_get_draw_page(). Плюс полная чистка всей ширины при инвалидации: старая полоса могла заходить под статус-текст, где щадящая чистка её не трогала; текст сразу перезапрашивается. Все 16 наборов host-тестов зелёные, check_bank_calls чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
b0e7130d0b |
SprPoP: мёртвое тело больше не приземляется в присед, надпись смерти не залипает
Два независимых фикса, оба проверены в MAME. 1. ПРИЗЕМЛЕНИЕ МЁРТВОГО (симптом «убили Кида, а он сел этажом ниже»). Оригинал спрашивает у приземляющегося, жив ли он (seg005:0173): вся развязка «мягко / средне / разбиться» отведена ЖИВОМУ, телу — своя ветка (добить HP, звук падения насмерть, seq_22). У нас развилки не было, и в глаза это не бросалось только из-за высоты: тело, сброшенное ударом с ОДНОГО ряда, набирает fall_y < 22 — урона нет, «последнее HP» не тратится, ветка «разбился» не выбирается никогда. Труп уходил в мягкое приземление и садился (кадр 109). Цена — один тест байта на вызов land(), то есть на событие касания земли, а не на кадр. Живой путь не изменился ни на операцию. 2. ЗАЛИПАВШАЯ НАДПИСЬ «Press Button to Continue». Счётчик кадров смерти живёт снаружи главного витка и потому переживает возврат на заставку. Ответ игрока кнопкой его обнулял, а выход по таймауту (24 с молчания -> title) уходил мимо сброса. Дальше счётчик оставался израсходованным на всю сессию, и в следующей игре ПЕРВАЯ же смерть мгновенно уводила в title, не показав надписи; лечилось только перезапуском программы. Сброс поставлен на входе в игровой маршрут — закрывает и остальные боковые дороги (выпадение за нижнюю границу, смена уровня). Здесь же довезена связка находок 12/13 аудита: смерть безоружного у обрыва уходит в свою последовательность (seq_81), а прижатие к полу осталось страховкой для прочих веток — снять его целиком не вышло дважды, подробности в комментарии guards.c. Тесты: t_death дополнен обеими сторонами развилки (мёртвый обязан разбиться, живой с той же высоты — сесть без урона), 18 проверок; все 16 наборов host-тестов зелёные. В доску записан CLIMB-VS-GUARD: Кид подтягивается к стражу этажом выше — у нас удар порой смертелен, в оригинале Кид срывается без урона. Цепочка засчитывания удара сверена с оригиналом и совпадает дословно, расходятся входные данные. Лучшая зацепка — ЗВУК: оригинал играет взмах клинка (11) при любом уколе, до всякой проверки попадания, а у нас слышен упор в стену (8) — значит страж не атакует, а сталкивается. Набор звуков проверен и не виноват. Отложено по решению пользователя. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY |
||
|
|
4e12aa50d1 |
SprPoP: падение сквозь стену больше не проходит
Портировано опциональное исправление SDLPoP (fix_glide_through_wall, seg005 в do_fall). В ванили персонаж, падающий после разворота в беге, может оказаться внутри кладки и лететь «в стене» — баг оригинала, воспроизведённый пользователем в игре и затем на host-тесте. Решением 2026-08-31 фикс взят в ТЕКУЩИЙ билд: играбельность важнее буквальности. Реализация вынесена отдельной функцией glide_through_wall_guard() в pop_map.c намеренно — при разделении VANILLA/ENHANCED это готовая точка отвязки, достаточно не звать её в ванильном режиме. ПРОВЕРКА. Набор t_wall был заранее написан так, чтобы сторожить ЧИСЛО заходов в кладку: до фикса их было ровно два из четырнадцати стартовых позиций, после — ноль. Остальные 15 наборов (в том числе phys с 1733 проверками и grab) остались зелёными. Живая проверка в MAME пользователем: корректно. Ожидание в тесте обновлено ОСОЗНАННО, прежнее число сохранено рядом отдельной константой с пометкой «сколько было до фикса»: оно измерено, и понадобится, когда появится режим VANILLA — там ожидание станет зависеть от режима. ЦЕНА: +57 байт в банке 3 (свободно 3043), резидент и куча не изменились. По скорости попадание только на кадры падения: пересчёт колонки — одно деление, дистанция до кромки считается лишь если персонаж действительно внутри кладки. Документы: в аудите находка 21 переведена в «портировано» с сохранением исходного разбора; в vanilla_vs_bugfixed статус фикса стал ВЗЯТ, сводка пересчитана (5 взято, 32 кандидата в ENHANCED). 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-эффектов. |
||
|
|
623199337e | SprPoP: HDD-раскладка и пути от каталога EXE | ||
|
|
602c3a20fa |
SprPoP: сняты последние глушения насоса — палитра на смене уровня и загрузки треков
Замер тем же способом (брейк на pop_sfx_fill + печать totalcycles, три окна по 800 вызовов = 9 секунд каждое, с уже снятыми глушениями): период насоса 245 760 тактов (медиана во всех окнах) максимум 245 832 / 270 096 / 311 346 (1,00 / 1,10 / 1,27 периода) пропущено порций 0 Порция считается пропущенной, когда зазор доходит до ДВУХ периодов: сама порция отдаётся железу за период до того, как она понадобится, поэтому опоздание обработчика на 1,1 мс — джиттер, а не потеря. Запас десятикратный. Поэтому сняты и оставшиеся места: * палитра через BIOS на переходе БЕЗ катсцены (sprpop_cold.c) — то самое, где ловили скрежет 2026-08-25; в комментарии помечено, что при возврате скрежета возвращать надо именно сюда; * массовые чтения треков и ресурсов под чёрным экраном (pop_intro.c): первая реплика PV, трек заставки между уровнями, «время вышло», ресурсы финала. Осталось только то, что глушит звук ПО СМЫСЛУ, а не ради защиты: выходы из сцен (pop_music_free + pause), уход в титры и в игру, выключение по Ctrl+S. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3bf28da8ee |
SprPoP: трек заставки переживает загрузку уровня — порядок как в оригинале
ЗАМЕР (MAME, 2026-08-28). Брейк на pop_sfx_fill с печатью totalcycles, 500 подряд вызовов насоса через всю загрузку уровня 1->2 под звучащий трек 27: медиана интервала 245 760 тактов, максимум 245 832 при дедлайне 251 000 (11,7 мс) — НИ ОДНОЙ пропущенной порции. Загрузка уровня насос не морит. Поэтому снята двойная заплатка: * pop_level_switch больше не глушит насос перед pop_level_load_num; * pre_cut_finish больше не досиживает трек на чёрном экране (это делалось только чтобы глушение не обрубило его на полуслове; ценой были ~8 секунд пустого экрана — трек 27 длиннее сцены: 10,7 с против 2,6 с). Взамен восстановлен порядок оригинала (seg003:68-108, play_level): катсцена возвращает управление сразу -> уровень грузится ПОД музыку -> ожидание конца трека на чёрном экране (порт `while (check_sound_playing())` + stop_sounds) -> показ уровня. Общая чернота теперь max(трек, загрузка), а не их сумма, и трек не обрывается. Ожидание со страховкой на ~20 с, чтобы потоковый трек не подвесил переход. Пропуск сцены по-прежнему обрывает музыку — как и было. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
89e9663753 |
SprPoP: сняты остальные глушения насоса — появление/закрытие меню, настройки, quicksave
Продолжение предыдущего коммита: заплатка стояла не в одном месте. * открытие меню (snapshot палитры + затемнение фона + первая полная перерисовка) и закрытие (menu_erase — те же две полностраничные копии) — DI у копира бандами по 16 строк (~1,6 мс против дедлайна 11,7 мс), а палитровое затемнение идёт и в титрах с катсценами, где музыку не рвёт; * сохранение POP.CFG и проба quickload — это десятки байт и open/close; прежняя осторожность «ESTEX уходит в диск надолго» относилась к загрузке НАБОРА страниц. Подтверждение с поля: HOF пишется под звучащий «won»; * quicksave/quickload (pop_qsave.c) — единственное снятое место, где по диску реально едут 16 КБ (~три периода насоса). Помечено в комментарии: если на F6/F9 появится скрежет, вернуть pop_sfx_pause/start точечно сюда. Дисковых глушений в живом звуке больше не осталось; те, что стоят на загрузке уровня и наборов страниц, не трогали — там они по делу. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ea07a8d7b0 |
SprPoP: меню больше не глушит звук на время перерисовки
ui_begin() звал pop_sfx_pause() перед полностраничной accel-копией, и это слышно: pause закрывает CBL целиком (cbl_close), курсор трека стоит, и короткая мелодия начала уровня замирала ровно на время перерисовки меню — а на перемещении по пунктам это повторялось на каждом кадре меню. Обоснование заплатки устарело. Лист, который зовёт gfx_copy_page (_bgi_scroll_rows_raw), режет DI бандами по 16 строк — ~1,6 мс против дедлайна насоса 11,7 мс (85,4 порции в секунду), между бандами есть окно прерываний. Тот же полностраничный копир каждый кадр делают игровой цикл и катсцены, и звук там не рвётся. Скрежет, под который заплатка ставилась, шёл от чтения насосом мусора и вылечен отдельно. Парный pop_sfx_start() в ui_end оставлен: он идемпотентен и чинит вход в меню при закрытом выводе (например сразу после загрузки уровня). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d23126983e |
SprPoP: музыка не замолкает в меню и в долгих фейдах
Насос CBL идёт из прерывания (и его запрос защёлкивается до подтверждения — проверено по MAME irqack_cb), поэтому сам звук главному циклу не нужен. А вот ПОТОКОВЫЙ трек — титульная тема играется кольцом — дочитывается с диска только в pop_music_service: страница ложится в слот, который насос уже прошёл. Этот вызов был лишь в игровом цикле, HOF и сценах, но не в меню и не в фейдах, а там главный цикл стоит секундами — кольцо опустошалось, и музыка замолкала до закрытия меню (жалоба пользователя). Добавлено: ui_wait_frame() в pop_menu.c (обслужить музыку + ждать фронт, заменил все ожидания кадра в меню) и pop_music_service() в fade_run и transition_ltr (pop_ui.c) — темп тот же, 50 раз в секунду, как в игровом цикле. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
50570881f5 |
SprPoP: подгонка молнии под музыку снята — причина устранена
PV_MAGIC_LEAD двигал жест заклинания (замах, шаг назад, вспышка) на 100 тиков (1,67 с) раньше сценария: сцена была render-bound, шла ~49 тиков/с вместо 60, а реплика играла по реальному времени — кода приходила раньше молнии. После перевода сцены на единые часы и блочную отрисовку подгонка стала вредной: молния била больше чем на секунду РАНЬШЕ коды (проверка пользователем). Ставим 0; константу оставляем на месте — если запись другого набора (mt32/ogg) разъедется, крутить надо её. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f25ed37d85 |
SprPoP: единые часы в сцене с Джафаром + конец уровня ждёт свою мелодию
1. СЦЕНА С ДЖАФАРОМ — ОДНИ ЧАСЫ, КАДРЫ ЛУЧА. Было две шкалы, и выбор между ними делался гонкой на старте сцены: одна выборка pop_snd_tick через кадр, «успел ли диск раскрутить звук». От прогона к прогону сцена шла то по тикам насоса CBL, то по кадрам луча, и кода реплики приходилась каждый раз на другое место картинки (наблюдение пользователя). Насос был нужен потому, что кадр рисовался дольше своего интервала; теперь отрисовка разложена по интервалам (pv_restore_bg), и счёт кадров честен — ветка насоса убрана целиком. 2. ПОДКАЧКА ТРЕКА — ПОЛСТРАНИЦЫ ЗА ШАГ (pop_music_load_step). 8 КБ ≈ 16 мс влезают в кадровый интервал, целая страница (33 мс) не влезала и растягивала кадр сцены. В сцене шаг остаётся безусловным (иначе реплики не успевали грузиться, memory pv_music_stall_regression) и оплачивается ровно одним интервалом. 3. КОНЕЦ УРОВНЯ ЖДЁТ МЕЛОДИЮ. Оригинал (seg003:387, play_level_2) не сменяет уровень, пока `check_sound_playing()`: экран пройденного уровня живёт с анимацией факелов, пока звучит трек. Мы уходили на смену сразу и обрывали мелодию на первых нотах. Теперь ждём большего из двух: pop_music_busy() и счётчика pop_endmus_left по длине записи (gen/pop_music_ticks.h) — второе нужно потому, что при ВЫКЛЮЧЕННОЙ музыке busy ложен, а оригинал выдерживает паузу и молча. Стартовый уровень возвращён на 1 (отладочный LEVEL=14 был только для замера). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3449f6f8c9 |
SprPoP: кадр катсцены — блоками по кадровому интервалу, а не «отрисовка плюс пять»
Катсцены шли ~8,2 fps вместо десяти. Делитель тут ни при чём: у оригинала cutscene_frame_time = 6 тиков по 1/60 с (reset_cutscene, seg001:527; его зовёт load_intro прямо перед сценой) = 100 мс, у нас 5 кадров луча по 50 Гц = те же 100 мс. Причина в том, ГДЕ отсчитывался интервал: сначала рисовали кадр целиком, и только потом ждали vsync и ещё четыре — то есть отрисовка ПРИБАВЛЯЛАСЬ к делителю. Полноэкранная gfx_copy_page стоит ~547 000 тактов = 1,27 кадра, отсюда 6+ кадров вместо 5 (замер PV-RENDER-BOUND: 49 тиков/с вместо 60). Теперь отрисовка разложена на блоки, каждый из которых заведомо влезает в кадровый интервал, и после каждого честно ждём vsync: фон верхняя половина -> vsync | фон нижняя половина -> vsync | актёры и декорации -> vsync (+ флип) | служебный блок (звук, подкачка трека) и добор до CUT_FRAME_VSYNC. Фон восстанавливаем только по картинке (200 строк с POP_YOFF), а не по всем 256: сверху и снизу чёрная рамка. Общий хелпер pv_restore_bg на все три цикла — cut_run (сцены 8/9/12 и финал), pre_room_animated (2_6/4/12 и time_expired) и intro_pv_draw_frame (сцена с Джафаром); последний теперь возвращает 3 кадра вместо 1 (13 вместо 11 со вспышкой), вызывающий их и так учитывал. Сверка делителей с SDLPoP: у всех сцен 6 тиков = 5 наших кадров; плавает только pv_scene (6 -> 8 -> 7, seg001:434/455) — это уже сделано кумулятивно через pv_seq_period + POP_T60, и минимальный бюджет (5 кадров) больше трёх съедаемых блоками. Замер в MAME пока НЕ сделан: до финальной сцены на отладочном старте LEVEL=14 не добраться (решётка перед комнатой 5 закрывается по таймеру, а Ctrl-комбинации через MCP-мост до игры не доходят). 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>
|
||
|
|
f76914849f |
SprPoP: колонки Controls сдвинуты вправо
Слева оставалось 6 свободных точек, справа 36 — блок выглядел прижатым к краю. Содержимое шириной 278 точек, сдвиг на +15 делает поля 21 и ~20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6cc8611d03 |
SprPoP: README для игрока и экран Controls в две колонки
README, который едет НА ДИСК рядом с программой, в двух версиях: README.TXT (английский) и README.RUS (русский). Краткое описание, запуск, полная раскладка управления, читы, файлы игры, чего не хватает против оригинала, благодарности. Русский обязан быть в CP866 — DSS и местные просмотрщики читают именно её, UTF-8 показался бы кракозябрами. Исходники лежат в dist/ как UTF-8 (чтобы читались в репозитории), перекодировка и CRLF делаются правилом Makefile. ПОРЯДОК В КОНВЕЙЕРЕ ВАЖЕН: sed ставит CRLF ДО iconv — BSD sed в UTF-8 локали отказывается работать с байтами CP866 («RE error: illegal byte sequence»), а с валидным UTF-8 работает. iconv БЕЗ -c намеренно: потеря символа должна ломать сборку, а не молча портить текст. ЭКРАН CONTROLS переписан в две колонки. В одну раскладка больше не помещалась: после фаз A-C клавиш стало вдвое больше, а по высоте есть только 200 точек вместе с заголовком. Слева игра (бег, лазание, бой), справа служебное и читы; высота блока считается по ДЛИННОЙ колонке. Строка «IN A FIGHT» — заголовок, а не клавиша: ниже неё те же стрелки и Shift означают другое, и без разделителя список читался бы противоречиво. Заодно экран перестал врать: там до сих пор висели P, I, U и F7/F8 — клавиши, переназначенные ещё в фазе A. Проверено в MAME: Backspace открывает меню, Settings -> Show key bindings показывает обе колонки целиком. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f8e96c0495 |
SprPoP: читы здоровья и пера — фаза C плана keys_plan.md
Shift+S — одна единица HP (малое красное зелье), Shift+T — поднять потолок HP (большая склянка), Shift+W — медленное падение (зелье пера). Механика у всех трёх в движке уже была: читы просто ставят те же переменные, что и подобранное зелье. СВЕРКА С SDLPoP дала деталь, которую легко проглядеть: ветка ЧИТА и ветка ЗЕЛЬЯ различаются. Зелье в seg006:1871 зовёт stop_sounds, а чит в seg000:838 — нет. Повторяем чит, а не зелье. Исключение — перо: там stop_sounds сидит внутри самой feather_fall(), поэтому остаётся. Вторая деталь: Shift+T НЕ проверяет, полное ли HP, — потолок растёт всегда (упираясь в POP_MAX_HITP = 10, как max_hitp_allowed оригинала). У Shift+S условие hitp_curr != hitp_max есть и сохранено. Модификаторы разведены: Shift+S делит скан-код с Ctrl+S (звук), поэтому требует отпущенного Ctrl; Shift+T делит с голым T (таймер), но тот сам требует отпущенного Shift. Проверено в MAME по памяти, а не на глаз: Shift+T 3/3 -> 4/4, Shift+W взводит pop_feather в 148, Shift+S на подпорченном отладчиком HP 2/4 -> 3/4. R (воскрешение) НЕ входит: это не ещё один чит, а правка модели смерти — окно неуязвимости, размазанное по кадровой цепочке (счётчик в seg003:512, пропуск пик и челюстей в seg000:1245, пропуск урона мечом в seg000:876, восстановление позы в seg006:1352). Задевает гейт «мёртв», про который memory pop_level_restart_scope прямо предупреждает. Отложено по решению пользователя. 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> |