Заголовок записи стал `<имя тега> — <дата> — <коммит>`.
Зачем коммит, если есть имя тега: тег можно передвинуть или
переименовать, а ревизия, на которой релиз собран, должна оставаться в
записи однозначно — по ней билд воспроизводится точно. Заодно это тот же
хеш, что зашит в EXE как BUILD_ID и виден на экране About, так что
принесённый пользователем скриншот сразу сопоставляется с записью
changelog.
Берётся именно `*objectname` (дереференс аннотированного тега), а не
`objectname`: последний вернул бы хеш самого объекта тега, а не коммита.
Команда целиком выписана и в шапке CHANGELOG, и в правиле в CLAUDE.md,
чтобы не восстанавливать её каждый раз.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1
До сих пор «что изменилось» восстанавливалось только из git log, а он
устроен по ПРИЧИНАМ правок, а не по тому, что заметит игрок. Заводим
отдельный файл — как это уже сделано у mdview2 (changelog в репозитории
ведётся на приложение, а корневой RELEASE_NOTES.md описывает тулчейн и
живёт своей жизнью).
Начат с v0.9.6: зацеп, зацикленный взмах клинка, кнопка в паласе. Более
ранняя история остаётся в git log и досках — переписывать её задним
числом смысла нет.
ВЕРСИЯ И ДАТА БЕРУТСЯ ИЗ GIT, а не проставляются руками: заголовок записи
= имя аннотированного тега, дата = дата его создания
(`git for-each-ref refs/tags/<тег>`). Так запись не может разойтись с
историей. Правило записано в CLAUDE.md рядом с дисциплиной досок, иначе
файл заведётся один раз и умрёт.
В запись идёт только то, что видно ИГРОКУ или меняет сборку/запуск, по
схеме «симптом -> причина одной фразой -> хеш»; полный разбор остаётся в
сообщении коммита и не дублируется.
Файл в UTF-8, как остальные доки SprPoP. CP866 у mdview2 — вынужденная
мера (его changelog читает сам MDView), здесь такого требования нет.
Ссылка добавлена во входные точки docs/README.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1
С обобщения HDD-сборки (ea8efdb) один образ рассчитан на несколько
приложений, и EXE переехал в подкаталог. В инструкции по запуску это не
отразили, поэтому старая последовательность (`d:` + `sprpop`) отвечает
`Bad command or file name`, а `dir D:\` показывает только каталог GAMES —
на это можно потратить время впустую.
Записаны и сам путь, и исправленная цепочка клавиш для моста.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1
Симптом (пользователь, 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
В записи стояло, будто оригинал играет звук 11 «сразу на кадре укола, ДО
проверки расстояния», то есть при ЛЮБОМ уколе, попал тот или нет. Это
было описание НАШЕГО кода, а не SDLPoP: ровно так звук у нас и стоял —
внутри ветки «не парировано» и до присвоения Opp.action = 99_hurt (снято
предыдущим коммитом). Классическое нарушение правила «источник истины —
SDLPoP»: за оригинал приняли собственную реализацию, и вывод из неё уехал
в доску как факт.
На деле звук 11 в оригинале защищён условием Opp.action != 99_hurt, то
есть звучит ТОЛЬКО на промахе: на попадании играет боль, на парировании
Opp.frame уже 161.
Что это меняет для самого бага:
- УЦЕЛЕЛО наблюдение «в оригинале 11, у нас 8» — это данные. Арифметика
приоритетов их подкрепляет: snd_prio[11] = 0x12 против snd_prio[8] =
0x4B (меньше значит важнее), взмах не мог быть заглушён упором в стену.
- ОТПАЛ вывод «оригинал ведёт стража атакой с промахом, а мы —
столкновением»: он опирался на неверную посылку.
- УСИЛИЛАСЬ версия про ПОРОГ ДИСТАНЦИИ: раз в оригинале слышен именно 11,
укол у стража СОСТОЯЛСЯ и ПРОМАЗАЛ. Прежняя «версия про столкновение»
вытеснила дистанцию зря — возвращаем её в главные подозреваемые.
Отдельно предупреждение тому, кто вернётся к багу: диагностика по звуку
теперь значит другое, старые заметки прогона будут вводить в заблуждение,
сцену надо переснимать. Положена таблица соответствий (11 = промах, 13 =
попадание, 8 = столкновение, тишина = ранний выход из check_hurting).
На механику урона правки не влияют — расхождение «удар убивает» остаётся
открытым как было.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1
Симптом (пользователь, 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
Регресс от 4e12aa5: glide_through_wall_guard() звался в do_fall сразу
после check_grab и отменял ТОЛЬКО ЧТО СОСТОЯВШИЙСЯ зацеп.
Причина в имени последовательности: seq_15 — это grab_ledge_MIDAIR, и
после удачного захвата Char.action == 3, то есть персонаж формально всё
ещё «в воздухе», уже подтянутый вплотную к кромке. А под кромкой в
комнатах оригинала стоит кладка. Guard видел ровно её (замерено на живой
сцене: t == TILE_WALL, d == 10, col 0, row 1), считал это пролётом сквозь
стену, отбрасывал персонажа на 5 пикселей назад и гасил fall_x. Зацеп
РИСОВАЛСЯ и тут же срывался — ловилось на длинном прыжке уровня 3
(комната 7) и в attract-демо (комната 2, прыжок с места на кромку 0,2,
после срыва Кид падал на пики).
ФИКС. check_grab() возвращает признак «зацепился», и при нём guard не
зовётся. Цена — один тест байта на кадр падения; фикс «падение сквозь
стену» цел, t_wall зелёный.
ПОЧЕМУ ПРЕЖНИЕ НАБОРЫ ЭТОГО НЕ ПОЙМАЛИ — и главный урок. t_grab, t_phys
(1733 проверки) и t_wall судят по «действие стало вис», а вис-то
наступал, он просто не жил. Первый A/B guard'а по этой же метрике дал
ЛОЖНО-ОТРИЦАТЕЛЬНЫЙ ответ, и подозрение с него было снято зря. Разница
между «зацепился» и «зацепился и держится» — это и есть разница между
багом и нормой.
Отсюда новый набор t_hang: критерий — вис ДЕРЖИТСЯ три кадра подряд, и в
отчёте различаются «не наступило» и «наступило и сорвалось». Окно — 34
фазы разбега из 41; при намеренно возвращённой поломке 0 из 41, тест
краснеет (проверено). Сцена — геометрия уровня 3 комнаты 7, приведённая
внутрь одной комнаты, чтобы шов не примешивался; кромка-пол и
кромка-решётка проверяются отдельно.
Живая проверка в MAME пользователем: корректно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T2k4VZrSyERwk6H97Sfi1
atlas значился 9193 Б против 9627 фактических, и расхождение держалось
через все правки этой сессии: замерял со СПРЯТАННЫМИ правками — те же
9627, то есть к ним оно отношения не имеет.
Похоже на механику самого инструмента: size_check.py читает .map С ДИСКА,
а не пересобирает. Программа, которую в тот раз не пересобрали, попадает
в эталон со СТАРЫМ числом — и дальше висит расхождением, пока её однажды
не соберут заново. Сегодня после make clean впервые за долгое время
пересобралось всё, и накопленный рост libc/libbgi стал видимым разом.
Записываю честное число. Полезный вывод на будущее: `make size-baseline`
имеет смысл только после полной пересборки тестов, иначе он фиксирует
смесь свежих и устаревших размеров.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Выхлоп tools/pop_quiet.py на текущих наборах: те же snd.arc и m*.bin, но
вдвое тише (out = in/2 + 0x40, тишина 0x80 остаётся на месте), плюс
неизменённые индексы. Раскладка совпадает с диском игры.
Это ПРОИЗВОДНЫЙ артефакт: восстанавливается из assets/packed одной
командой `python3 tools/pop_quiet.py` за секунды. Лежит в репозитории по
той же логике, что и assets/packed — чтобы вариант был под рукой без
пересборки; если решим, что 3,6 МБ того не стоят, снимается одним
git rm --cached плюс строка в .gitignore.
На образ игры dist/ автоматически не попадает: make hdd берёт assets/packed
через build/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Правки пользователя, разобранные по диффу и закоммиченные как есть.
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
Правка пользователя, разобранная по диффу и закоммиченная как есть.
Было «всё или ничего»: --bank-data уводил писучие данные В СТРАНИЦУ для
ВСЕХ банков сразу. Теперь флаг принимает необязательный номер и
повторяется: --bank-data 5 --bank-data 6. Без аргумента поведение прежнее
(все банки), поэтому существующие сборки не меняются.
Зачем поштучно: данные в странице банка НЕ ВИДНЫ снаружи, поэтому модуль с
экспортируемым глобалом обязан остаться на общем _DATA в W1/W2 — а его
сосед в это же время может держать большой приватный буфер вне резидентного
бюджета. Одним флагом на всю программу это не выражается.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Правка пользователя, разобранная по диффу и закоммиченная как есть.
Программа может временно маппить свою EMM-страницу в окно 0 (атласы
спрайтов, gfx_w0_map). Пока страница там, любое прерывание уходит на
#0038 ЭТОЙ страницы, поэтому gfx_w0_page_prepare прошивает туда переход на
_gfx_w0_isr. Сам стаб живёт в _CODE, а в режимах small и huge _CODE
начинается с 0x4100 — то есть попадает в W1, окно, которое DSS перемаплет
на время СВОИХ вызовов. Прерывание в этот момент уходит по адресу,
которого сейчас нет: W1 читается как #FF.
Ловится тяжело: собирается молча, проявляется недетерминированно —
зависанием примерно на каждом третьем холодном старте. Поймано вживую
2026-09-01.
check_w0_isr.py берёт адрес __gfx_w0_isr из .map: символа нет — W0-путь не
слинкован, молчим; адрес >= 0x8000 — он в W2, всё хорошо; иначе WARNING.
Сборку не валит намеренно: свои страницы в W0 кладут не все. Вызов
добавлен в app.mk рядом с check_bank_calls.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Берёт то, что уже лежит в assets/packed, делает тише и кладёт в dist/ в
раскладке диска, перезаписывая прежнее. Оригиналы не трогаются, поэтому
скрипт идемпотентен: сколько раз ни запусти, громкость упадёт один раз.
Формула — та, что предложил пользователь: PCM у нас 8 бит без знака с
тишиной 0x80, значит вдвое тише это out = in/2 + 0x40 (беззнаковый сдвиг).
Таблица на 256 значений строится floor-делением отклонения от центра, что
при gain = 1/2 совпадает с этой формулой байт в байт — то есть результат
можно сверять с реализацией на Z80 (srl a / add a,#0x40). Тишина остаётся
тишиной при любом gain, клиппинга нет по построению.
Пересчитываются: MUS/m*.bin целиком (чистый PCM) и ТОЛЬКО тела записей
PBA1 в SND/snd.arc — заголовок и выравнивающие хвосты остаются как есть,
там нули упаковщика, и пересчёт превратил бы их в 0x40. Индексы
копируются без изменений (длины те же), но с проверкой магии.
Прогон на текущих наборах: 10 записей архива и 22 трека, пик 128 -> 64,
размеры байт в байт прежние, заголовок PBA1 не тронут.
Тест закрепляет формулу и неприкосновенность служебных байт; make
test-tools — 45 тестов. Раскладка dist/ добавлена в tools/paths.py, по
правилу «пути знает один файл».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Два бага одного пути завершения, оба видны только на железе.
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
make SND_SRC=msdos MUSIC_FMT=flac resources-rebuild — полная перегенерация
всех ресурсов. Изменился только SND/: у MSDOS-набора оцифровка полнее,
чем у SDLPoP (там пуст звук 48 spiked и короче 10 sword_vs_sword — разбор
в docs/sound_plan.md).
Музыка и остальные архивы после перегенерации совпали с закоммиченными
байт в байт — набор flac и есть тот, из которого они сделаны.
Умолчание сборки НЕ меняется: SND_SRC ?= sdlpop, чтобы клон без
оригинального дистрибутива DOS собирался целиком (make fetch). Вернуть
прежний набор — make resources-sound после make clean stamps, или
make SND_SRC=sdlpop -B resources-sound.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
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
От запуска до проявления титула проходило ~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
cbl_close() не пишет в порт управления ноль: пока gfx_wait_vsync держит
ссылку ради бита луча (в PoP её берёт pop_pace_arm на всю программу),
bit7 остаётся включённым. А буфер CBL (256 слотов) при включённом bit7
крутится по кругу и не чистится ничем — железо бесконечно поёт хвост
последнего сэмпла, тоном и громкостью по последней мелодии. Пойман
пользователем на железе: Ctrl+S во время музыки и пропуск заставки
давали ноту до следующего cbl_open.
В MAME не воспроизводится: "немым" кодом частоты был reserved-код 2, у
которого divs[2]==0 и таймер не заводится вовсе. На железе reserved-коды
не определены, ЦАП тактируется — тишина держалась на свойстве эмулятора,
а не железа.
Теперь тишину даёт СОДЕРЖИМОЕ БУФЕРА:
- _cbl_port_sync() после каждой записи в порт управления зовёт
_cbl_prime(0x80). Это закрывает и паузу звука, и включение bit7 ради
луча на холодном старте (в буфере лежал мусор от прошлой программы), и
полное выключение — при bit7=0 те же 256 записей уходят прямо в ЦАП
обычного COVOX и центруют его, снимая щелчок;
- _CBL_VSYNC_FREQ переведён с reserved-кода 2 на документированный 8
(7,8125 кГц): поведение определено и на железе, и в MAME, прерывания
по-прежнему выключены (bit4=0), а бит 7 порта 0xFE трамплин смотрит
только при живом хуке насоса;
- cbl_close() зовёт sync внутри той же DI-скобки, где снимает хук, иначе
насос долил бы буфер уже после заливки.
Цена: +2 Б программам со звуком, +11 Б графическим (тянется _cbl_prime
следом за gfx_wait_vsync), 256 OUT'ов (~0,3 мс) на редкое событие —
эталоны cblstream/cbltest/cblwav/gfx_dbuf обновлены. Рост atlas в
size-check к этой правке отношения не имеет (замерен тот же и без неё).
Проверено: кодоген _cbl_port.asm; SprPoP пересобран и прогнан в MAME
(титры → пропуск заставки → уровень 1, пейсинг по лучу жив). Сам баг
проверяется только на железе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSYUpuaQpKr48kBav2iiV4
Четыре правки прогона 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
Разбор без правок кода; все три отмечены как задачи по решению пользователя.
SND-SPEAKER-38 (TASKS_OPEN, P1). Надпись «Press Button» мигает молча.
Наш код не виноват: pop_dead_prompt зовёт звук 38 на каждом появлении,
как оригинал, но слот 38 в наборе ПУСТ. Причина — три параллельных
набора звука у оригинала: оцифровка (0-23, 44-49, 51), мелодии (24-43
частично, 50, 52-56) и ноты PC-спикера (весь диапазон). Звук 38 есть
ТОЛЬКО среди нот спикера, поэтому провалился между нашими конвейерами.
Полная ревизия: не оцифровка и не мелодия — номера 31, 34, 38, 42, из них
31/34/42 пустые заглушки, реально звучит ровно один — 38. Решение
выбрано пользователем: синтезировать ноты в PCM и класть в наш атлас;
формат разобран по спецификации Princed и записан в задачу.
TIME-SPEED (TASKS_OPEN, P1). Часы уменьшаются на каждом логическом кадре
(как оригинал), но длину кадра у нас меняет режим скорости — в FAST
минута проходит на треть быстрее. Разная длина кадра в игре и в бою есть
и в оригинале (поправка пользователя), поэтому замедление часов в бою не
трогаем; вопрос только в наших добавочных режимах. Два варианта с
рекомендацией оставить как есть.
HP-BAR-RESTART (BUGS_OPEN). После гибели и Ctrl+A (у нас это рестарт
уровня) на одной из страниц остаётся полоса по результатам боя. Механизм
перерисовки на месте — счётчик страниц, все холодные пути его взводят.
Подозреваемый: пока висит статус-текст, стирание чистит только края и не
трогает середину, а полоса стража при большом запасе HP заходит именно
туда.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
Два независимых фикса, оба проверены в 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
Портировано опциональное исправление 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
Документ vanilla_vs_bugfixed.md дополнен полным перечнем опциональных
исправлений оригинала, какие есть в SDLPoP, с их статусом у нас и
местом в коде, где решение принято.
Расклад: 4 взяты (дверь выхода, звуки ворот, перо только для Кида,
приоритеты звуков), 4 сознательно оставлены ванильными (падение на
стража, прыжок через стража, трюк 35, кровь скелета), 1 в работе
(падение сквозь стену), 33 не реализованы — кандидаты в ENHANCED.
ГЛАВНОЕ СЛЕДСТВИЕ: наш билд — это не VANILLA, а «ваниль плюс четыре
исправления». При разделении режимов придётся пройтись по уже сделанным
отступлениям и распределить их, иначе текущее поведение нельзя считать
эталоном ни для одного режима.
ИСПРАВЛЕНА МОЯ НЕВЕРНАЯ ОЦЕНКА. Ранее было записано, что фиксы,
требующие правки байткода seqtbl, у нас недоступны без переделки
конвейера данных. Это неверно: байткод можно менять и у нас. Лучший
способ — держать ОБЕ версии в одной странице EMM: kid.ani занимает около
4 КБ при странице в 16 КБ, так что обе помещаются рядом, а переключение
режима сводится к смене базового смещения — без патчей и с мгновенным
откатом. У SDLPoP, к слову, рабочая таблица и неизменная копия
оригинала тоже существуют раздельно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
КОД ИГРЫ НЕ МЕНЯЛСЯ. Новый набор 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
КОД ИГРЫ НЕ МЕНЯЛСЯ — правка находки 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
Правка 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
tests/host не линковались: не разрешались pop_chdir_home (его тянет
pop_kboot из банка 10) и kbd_raw_keypad_as_ext (тянет pop_ctrl из libc).
Поломка предсуществующая — воспроизводится и на коммите до всех правок
этой сессии. Ни файловой системы, ни клавиатуры в хостовых тестах нет,
поэтому обе заглушены пустышками в общей обвязке.
Теперь все пятнадцать наборов проходят: geom 3144 проверки, phys 1733,
char 72, grab 55, jaffar 44, shadow 45, app 58, cfg 51, demo 30, flow 27,
timer 28, cutscene 13, gate 10, mouse 17, wall 1.
Это условие для дальнейшей работы: логику движка снова можно проверять за
секунды, без сборки образа и ручного прохождения в MAME.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
КОД НЕ МЕНЯЛСЯ. Разбор одиннадцати находок рангов А и Б: что менять, во
что это обойдётся по скорости и памяти, каков риск.
СНЯТО ГЛАВНОЕ ПРЕПЯТСТВИЕ. Обоснование двух упрощений (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
КОД НЕ МЕНЯЛСЯ. Построчный разбор наших реализаций против оригинала:
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
Часть 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
Часть 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
В репозитории нет ни байта чужих данных, но есть знание, откуда их взять:
tools/fetch_orig.py качает SDLPoP (ресурсы + исходники-эталон) и записи
саундтрека, причём адрес архива музыки читает из самого SDLPoP
(ReadMe.amigaos4, секция «AUDIO IS SLOW/AWFULL»); запасной адрес вшит
константой. Цели: make fetch / fetch-sdlpop / fetch-music / fetch-check /
fetch-list.
Версия SDLPoP пишется в .fetch.json вместе с манифестом sha256 всего
дерева. По нему следующий fetch отличает НАШИ отладочные врезки
(POP_TRACE — покадровая трасса Кида, дампы палитры и спрайтов) от
нетронутых файлов и не сносит их молча: без --force каталог не трогается
вовсе, с --force старая копия уезжает в бэкап .cache/.
MSDOS/ не качается и НЕ НУЖЕН: уровни и оцифровка берутся из SDLPoP.
У SprPoP теперь свой .gitignore, написанный так, чтобы стать корневым при
выделении в отдельный репозиторий (пути от корня приложения, ничего про
applications/). Из корневого .gitignore тулчейна SprPoP-секция убрана,
чтобы две копии не разъезжались. Заодно закрылась дыра: шаблон
applications/*/*/*.exe не покрывал артефакты в корне SprPoP.
assets/orig/README.md выведен из-под игнора — без него в чистом клоне не
написано, откуда брать данные, а это и есть смысл затеи.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY
Добавить общий каталог назначения для HDD и удалить локальную копию упаковщика.\n\nУбрать устаревшие generated-имена ресурсов, выводить число страниц Kid из kid.arc и ограничить звуковую таблицу горячим модулем.\n\nЗафиксировать планы runtime-индексов музыки и PCM-эффектов.
Замер тем же способом (брейк на 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>
Записана с оговорками, найденными при сегодняшнем разборе: загрузку придётся
разрезать на дисковую и палитро-экранную половины, шаг подкачки держать
полустраничным, проверить EMM-бюджет на два уровня разом. Половина идеи уже
работает — трек заставки играет поверх загрузки (порядок оригинала, замер
насоса приложен в записи).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ЗАМЕР (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>
Обе записи закрыты сегодняшними правками: вторая шкала по насосу удалена
вместе с гонкой, которая её выбирала, а «сцена дороже бюджета» оказалась не
ценой кадра, а местом отсчёта интервала. Исходные разборы оставлены под
заголовками — они объясняют, как искали.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Продолжение предыдущего коммита: заплатка стояла не в одном месте.
* открытие меню (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>
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>
Насос 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>
PV_MAGIC_LEAD двигал жест заклинания (замах, шаг назад, вспышка) на 100
тиков (1,67 с) раньше сценария: сцена была render-bound, шла ~49 тиков/с
вместо 60, а реплика играла по реальному времени — кода приходила раньше
молнии. После перевода сцены на единые часы и блочную отрисовку подгонка
стала вредной: молния била больше чем на секунду РАНЬШЕ коды (проверка
пользователем). Ставим 0; константу оставляем на месте — если запись
другого набора (mt32/ogg) разъедется, крутить надо её.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Катсцены шли ~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>
Корень двух багов 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>
dist/SprPoP/** — снимок готового к раздаче каталога (exe + упакованные
ассеты) от старой раскладки. Ничто его больше не собирает: `make`
кладёт результат в build/, образ — в build/hdd/. В dist/ остаются
только ИСХОДНИКИ README (README.txt / README.ru.txt), из которых
Makefile печёт README на образе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Замороженная roomtest: убраны рабочие заметки прошлых сессий
(NEXT_SESSION.md, «new bugs») и скриншоты уже закрытых багов;
в Makefile — PROF=0 по умолчанию и путь к HDD-образу.
applications/PoP/docs/keys.txt — раскладка управления оригинального
PoP (справочник для порта).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:00:15 +03:00
195 changed files with 6895 additions and 1337 deletions
| `src/` | рукописный C: главный цикл `sprpop.c`, холодная половина `sprpop_cold.c`, движок `pop_*.c` |
| `gen/` | генерируемые заголовки (индексы архивов, таблицы кадров, шрифт, палитры). **Руками не править** — их печатают упаковщики; лежат в репозитории, потому что без них `src/` не собрать |
| `assets/orig/` | оригинальные данные (SDLPoP, MSDOS, записи музыки). Вне репозитория — копирайт; что и откуда взять, написано в `assets/orig/README.md` |
| `assets/orig/` | оригинальные данные (SDLPoP, записи музыки; MSDOS — необязательно). Вне репозитория — копирайт; вместо них в репозитории лежит `tools/fetch_orig.py`, который их качает (`make fetch`). Правила — в `assets/orig/README.md` |
| `assets/packed/` | то, что ложится на диск игры, уже в раскладке диска (`BG/`, `KID/`, …). В репозитории — иначе из чистого клона не собрать |
| `tools/` | конверторы ресурсов. Раскладку путей знает ОДИН файл — `tools/paths.py`; менять пути нужно там, а не в отдельных упаковщиках |
| `tools/` | конверторы ресурсов. Раскладку путей знает ОДИН файл — `tools/paths.py`; менять пути нужно там, а не в отдельных упаковщиках. Адреса ВНЕШНИХ источников — так же в одном: `tools/fetch_orig.py` |
| `pop_cdraw.c/.h` | ОТРИСОВКА персонажей — одна на всех Char (порт `add_kid_to_objtable`/`add_guard_to_objtable`): спрайт, clip_char, брызги, клинок, heal; полоса HP и палитры соперника. Там же ПРОПУСК неизменившегося кадра (`pop_char_skip_mask`). |
| `pop_sfx.c` + `pop_sfx_cold.c` | Звук: насос CBL в резиденте + холодная загрузка набора. Раскладка эффектов НЕ компилируется в EXE — читается с диска (`SND/snd.idx`, формат `PSI1`); см. `docs/sound_plan.md` §10. |
| `pop_music.c` | Музыка (банк 9): загрузка треков, кольцо для финальной темы. Длины и длительности — из `MUS/mus.idx` (EMM-страница + accessor `pop_music_info`), в EXE их нет; см. `docs/sound_plan.md` §11. |
| `pop_snd_tbl.h` + `pop_snd_data.c` | Тип записи набора и инварианты (рукописный заголовок) + резидентные `pop_snd_tbl`/`pop_snd_page`/`pop_snd_pages`. Размер записи 5 байт — часть дискового контракта, проверяется статически. |
| `pop_map.c/.h` | Коллизия с картой + физика падения/приземления/стены + loose-полы (`seg005/006/007`). |
| `SDLPoP/src/` | Исходники SDLPoP. Нужны не только упаковщикам (`menu.c` и `seg009.c` содержат встроенные шрифты, из `seg005/seg006` берутся таблицы кадров и `seqtbl`), но и как **эталон механики**: расхождение в поведении всегда сверяется с ними | тем же `make fetch-sdlpop` |
| `PoP1_DOS_music/` | Записи музыки DOS-версии в четырёх наборах: `flac/` (по умолчанию), `mp3/`, `ogg/`, `ogg_MT-32/` (исполнение Roland MT-32). Имена треков во всех наборах одинаковые; какой брать — задаёт `MUSIC_FMT` | `make fetch-music`. Адрес архива скрипт читает из самого SDLPoP (`ReadMe.amigaos4`, секция «AUDIO IS SLOW/AWFULL»), запасной — popot.org |
| `MSDOS/` | **Необязательно.** Оригинальная поставка PoP 1.0 для MS-DOS. Используется только `digisnd*.dat` со звуком, и только если попросить: `make SND_SRC=msdos` | Не качается — дистрибутив игры, положить руками |
## MSDOS: зачем он и почему без него живётся
По умолчанию **всё** берётся из SDLPoP — и уровни, и оцифровка звука. Так
сборка работает на чистой машине сразу после `make fetch`.
`MSDOS/` даёт ровно одно: более полный набор оцифрованных эффектов. Из 31
звука 28 совпадают побайтно, а три — нет, и все три лучше в дистрибутиве:
`spiked` (48, насаживание на пики) в наборе SDLPoP длиной 7 сэмплов, то есть
его нет; `sword_vs_sword` (10) короче; `sword_moving` (11) — другая запись.
Разбор — `docs/sound_plan.md`. Включается это явно:
```
make SND_SRC=msdos resources-sound # перепаковать звук из дистрибутива
make SND_SRC=msdos # и собрать с ним
```
Источник входит в имя stamp'а, поэтому переключение `SND_SRC` само делает
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.