Commit Graph

8 Commits

Author SHA1 Message Date
snark13 16d3262340 Звук: насос CBL качает через W3 — вход в BIOS ломал W0
СИМПТОМ: затемнение (fade) хрипело — одинаково с играющей музыкой и в
тишине.  Ключевое наблюдение пользователя: повтор тишины обязан звучать
тишиной, значит дело не в недоливе буфера.

ПОИСК: отладочные клавиши, каждая делала ровно один кусок fade.  Ожидание
кадров — чисто; чтение палитры, запись палитры и 512 вызовов
bios_get_place() (видео вообще не трогает) — скрежет во всех трёх.
Последнее и решило: виновата не палитра, а ЛЮБОЙ вызов BIOS.

ПРИЧИНА: `rst 8` раскрывается в `out ($7C),a`, который включает системное
ПЗУ и перестраивает окно 0 (MAME sprinter.cpp, update_memory: m_pages[0] +
m_bank_view0.select).  ПЗУ ложится ПОВЕРХ страничного регистра, поэтому
запись в порт 0x82 из прерывания бесполезна — OTIR вычитывает ПЗУ и
отдаёт его в звук.  Отсюда же старое правило «глушить CBL на время
загрузки файлов»: причина была не в том, что ESTEX долго занимает CPU.

РЕШЕНИЕ (идея пользователя): качать через W3.  Он управляется только
портом 0xE2, подмену из прерывания никто не перекрывает, а BIOS во время
нашего ISR не исполняется — окно возвращается до выхода, и для него
подмена невидима.  После этого BIOS безопасен везде.

* pop_sfx.c — насос берёт взаймы W3 вместо W0, чтение по 0xC000 + смещение.
* pop_ui.c — буфер палитры по фиксированному 0x4000 (эти 256 байт DSS
  занимает только при загрузке программы): 256 байт со стека долой.
* libbgi/common/gfx_pal_write.c — запись палитры прямо в видеопамять,
  минуя BIOS.  Писалась как обход скрежета, после переноса насоса не
  нужна; оставлена как более быстрый примитив (2,5 тыс. тактов на 64
  цвета против 10,8 тыс. у BIOS) с честной шапкой.  Адресация разобрана
  по исходникам BIOS (FUNC_SCREEN.ASM): Port_Y = индекс цвета, адрес
  0xC3E0 + pal*4, порядок R/G/B/Y.
* libc/video/pal_get.c, pal_load.c — в шапках зафиксировано, что BIOS
  выбирает окно ПО АДРЕСУ БУФЕРА (`BIT 7,H`).
* pop_intro.c — при пропуске интро клавишей не глушился CBL, и следующая
  загрузка уровня шла с открытым буфером; добавлен pop_sfx_pause.
* pop_ctrl.c — убраны отладочные «осторожные шаги» на J/L (эмуляция
  Shift+стрелка для MAME), у них и стоял TODO.

Разбор целиком — docs/sound_plan.md §5.  Бюджет: _CODE 23966, куча 245 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:36:47 +03:00
snark13 ba3aca07bf Архивы ресурсов PBA1: звук одним файлом, музыка потоком; ALLOCS 6000
ЗАМЕР, из которого всё выросло (MAME, 2026-08-25): `open` стоит 51,4 мс,
а чтение 16 КБ из уже открытого файла — 32,6 мс.  То есть 61 % времени
загрузки уходит на ОТКРЫТИЕ, а не на данные, и чистая скорость чтения —
491 КБ/с, а не 260.  На диске игры 343 файла; одна только стартовая пачка
(kid+shadow+bg+sound+guard, ~142 файла) — это 7,3 секунды чистого open.

* toolchain/pop_pack_arc.py + pop_arc.c/h (банк 8) — формат PBA1:
  заголовок 512 Б, магия, count, записи по 4 байта (offset в СЕКТОРАХ —
  архив бывает больше 64 КБ; size в байтах — ресурс всегда <= 16 КБ,
  он обязан лезть в EMM-страницу).  Таблицу держит вызывающий на своём
  стеке: в W2 её класть нельзя, а стека в точке загрузки израсходовано
  75 байт из 1279 (замер там же).
* libc: bank_read_page — чтение из УЖЕ ОТКРЫТОГО файла в EMM-страницу.
  bank_load_file переписана через неё, поэтому дублирования работы с W3
  не осталось и резидент почти не вырос.
* Набор эффектов: 10 файлов -> SND\snd.arc, один open вместо десяти.
* Музыка: трек лежит ОДНИМ файлом и читается порциями по странице —
  нарезка на куски больше не нужна.  Этот же путь понадобится финалу
  `won` (1,2 МБ), который в память целиком не влезает.
* Менеджер звука приведён к оригиналу: играющая музыка участвует в
  таблицах приоритета наравне с эффектами (seg000:1672 + data.h:433).
  Почти вся музыка НЕперебиваема, поэтому поверх неё эффект не стартует —
  ровно как в SDLPoP; раньше эффект её перебивал.
* ALLOCS 3000 -> 6000 (решение пользователя): сборка +20 с, резидент
  -161 байт.  Куча 87 -> 248 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:13:25 +03:00
snark13 34ba5f71d5 Музыка: PCM-трек через CBL, первый экран истории озвучен
Путь C из sound_plan.md вместо ранее выбранного A (ноты спикера на AY).
Причина смены: появились готовые записи DOS-версии, и строка «нужен
синтезатор на хосте», из-за которой C считался дорогим, отпала —
осталось `ffmpeg -ac 1 -ar 10937 -f u8`.  Оцифровки музыки в самой игре
нет вовсе: digisnd*.dat содержит только эффекты, музыка лежит MIDI под
Adlib, так что конвертировать ресурс игры всё равно было не из чего.

* toolchain/pop_pack_music.py — запись flac -> сырой u8 на 10 937,5 Гц
  (частота CBL, чтобы её не переключать никогда), нарезка на куски
  MUS\m<id>_<nn>.bin по 16 КБ: bank_load_file читает файл только целиком
  и только в одну страницу.  Каталог -> pop_music_tbl.h; длина трека
  хранится ПОРЦИЯМИ по 128 байт — 169 КБ в uint16 не влезает, 1350
  блоков влезает (32-бит арифметики на Z80 избегаем).
* pop_music.c (банк 9) — загрузка трека в EMM и курсор; pop_sfx_fill
  (резидент) получил третий источник: эффект важнее музыки, музыка
  важнее тишины.  Эффект музыку не сбрасывает — её курсор стоит, пока
  тот доигрывает, и она продолжается с места.
* story_1_absence звучит на первом экране истории, где была тишина.
  Грузим ПОСЛЕ сборки картинки: чтение идёт через ESTEX, и при открытом
  CBL насос не успел бы долить блок.

Проверено записью звука из MAME: корреляция огибающих с эталоном 0,836
при сдвиге 1,8 с, RMS 22,6 против 18,3 (разница — 8-битное квантование).
Эффекты двери в PV-сцене после трека звучат по-прежнему.

Бюджет: резидент +83 Б кода и +25 Б данных, запас W2 151 Б (кучи в
приложении нет, malloc не слинкован); банк 9 занят на 88 %.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 15:21:22 +03:00
snark13 47a4b084c4 Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):

* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
  снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
  (UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
  равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
  ABOUT.  About показывает тот же текст, что стартовый Sprinter screen,
  минус строка про клавиши — общий about_text(), чтобы экраны не
  разъехались.  CONTROLS собирается таблицей и центрируется по
  фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
  (SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
  а не пункт меню.  POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
  модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах.  Нижняя:
  порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
  24 тика).  Верхняя отладочная переведена с палочек на текст
  «Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
  своим форматированием чисел (без printf и без деления).
  Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
  не растёт, весь рендер в банке.  Бюджет после правок не изменился
  (_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
  всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
  не блитятся вовсе, а вход в комнату заливает только игровое поле
  (pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
  ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
  статус-строке: геометрия, семантика text_time_total как идентификатора
  сообщения, мигание, рестарт по истечении 36/288.

Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:58:36 +03:00
snark13 f4b4852d51 QuickSave F6/F9 в roomtest; bank_load_file/bank_save_file/gfx_w0_page_prepare; sprinter-cc: авто n_banks
roomtest:
- QuickSave/QuickLoad (F6/F9): снапшот 'POPQ' v3 в POP.SAV/POP.BAK на HDD,
  транзакционная запись (POP.NEW -> rename, откат при ошибке), XOR-контрольная
  сумма payload'а; сериализация всех игровых переменных через W0-примитивы
  pop_qs_*; pop_qsave_process() на границе кадра вне Char-окон
- pop_qsave_restore_room(): полная перезагрузка комнаты после загрузки
  (карта/края/швы, сброс bake-кэша, перерисовка обеих страниц, инвалидация
  кэшей спрайтов и HP)
- сериализаторы в pop_map/pop_loose_mob/pop_trob/pop_guard_ai
  (+ восстановление инвариантов: mobs_live, trob_drawn, redraw)
- immortal-чит 2 уровня: уровень 2 поглощает только малый урон Kid'а

libc/libbgi:
- bank_load_file()/bank_save_file() — резидентное файловое I/O в банк,
  без правила W3 (путь читается до переключения страницы)
- gfx_w0_page_prepare(page) — подготовка W0-окна (IRQ/NMI-стабы) одной
  функцией; atlas_load.c и roomtest переведены на новые примитивы;
  ручные ISR-стабы удалены

sprinter-cc / сборка:
- --bank N=FILE.c: автогенерация n_banks (_n_banks_auto.c), ручные
  const n_banks удалены из тестов
- roomtest/app.mk: ресурсы через stamp-файлы (.resource-stamps/) — один
  запуск упаковщика на группу вместо N под -B; HDD_PACK_ARGS
2026-08-22 11:53:10 +03:00
snark13 a630568a8b Звук PoP: приоритеты и перебиваемость вместо «всегда перебивать»
Пользователь услышал расхождение с SDLPoP: у нас решётка обрывалась
приземлением Кида, в оригинале доигрывает до конца, а приземления не
слышно.  Оказалось, упущен целый механизм.

play_sound (seg000:12C5) НЕ играет, а только номинирует кандидата на
кадр — из нескольких выживает важнейший (меньше prio = важнее, при
равенстве последний).  play_next_sound (seg000:1304) раз в кадр решает,
запускать ли: можно, только если ничего не играет ЛИБО текущий помечен
перебиваемым и новый не менее важен.  Иначе номинант выбрасывается —
очереди в оригинале нет.

Отсюда всё, что слышно: gate_closing_fast неперебиваем и доигрывает
целиком; челюсти (prio 0x10) всегда важнее решётки (0x32), поэтому
решётка звучит только в паузах между укусами.

Таблицы из SDLPoP с учётом fix_sound_priorities (в его config.h он
определён безусловно).  Створка двери уровня — единственная запись,
правимая на ходу, вынесена в отдельный байт.  Добавлен пропущенный
stop_sounds на завершении открытия двери (seg007:455).

Проверено записью MAME: старт уровня 1 был 135+210 мс (решётка, обрезанная
на 80 мс), стал один всплеск 455 мс с корреляцией огибающей +0,889 со
звуком 6.

Заведён BUG-SND-FIRSTRUN: искажение первого эффекта при первом запуске
после загрузки системы — вероятно, лечится _cbl_prime, но проверить можно
только на железе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:46:04 +03:00
snark13 e0c86a96ef Ctrl+S — вкл/выкл звук; чит «выдать меч» убран
Порт Ctrl+S из SDLPoP (seg000:657).  Выключение реально закрывает CBL, а
не глушит сэмпл: иначе насос продолжал бы отдавать блоки тишины и платить
те же 3,26 % процессорного времени.

Флаг намерения pop_snd_want отдельно от pop_snd_ok: последний гасит
служебная пауза на время загрузки уровня, и правь Ctrl+S только его —
первая же смена уровня вернула бы выключенный звук.

Индикация: пурпурная палочка в борте, когда звука НЕТ (включённый слышно
и так, а молчание неотличимо от «нечему звучать»).

Чит S «выдать меч» был отладочным и больше не нужен — снят, клавиша ушла
под звук.

Замер цены звука — sound_plan.md §9: 8 001 такт на прерывание при периоде
245 759 (3,26 % времени), +2,2 % к работе кадра в сцене 11/15, период
кадра не сдвинулся ни разу.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:49:33 +03:00
snark13 8ea4c32e51 Звуковые эффекты PoP: оцифровка оригинала через CBL
Набор digisnd1..3.dat приведён упаковщиком к 10 937,5 Гц (частота
железа), склеен в 8 EMM-страниц с выравниванием каждого звука на 128 —
размер блока запроса CBL, поэтому ни один блок не пересекает границу
страницы и проигрыватель не знает слова «стык».

Насос (pop_sfx.c) резидентный: его зовут из прерывания CBL, из горячих
мест физики и из play_seq.  Тишину льём свою (первый блок набора), а не
через CBL_UNDERRUN_SILENCE с его malloc — куча в резиденте W2 тесная.
Открытие CBL разведено с загрузкой (pop_sfx_start отдельно от
pop_sfx_init): пока ESTEX читает файлы, насос не успевает долить блок и
железо крутит хвост буфера — на слух скрежет.

Разведены все места play_sound() SDLPoP, у которых есть оцифровка
(id 0..23, 44..49): посадки, падение, удары о стену, зацеп, тряска и
провал плит, ворота, дверь уровня, пики, чомпер, кнопки, боёвка, меч,
зеркало, скелет, зелье.  Таблица соответствий — docs/sound_plan.md §8.
Музыкальные id остаются с нулевой длиной до фазы музыки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:16:43 +03:00