From decbec79de16f89031670b662957722e1297be6f Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 31 Aug 2026 22:31:32 +0300 Subject: [PATCH] =?UTF-8?q?SprPoP:=20=D1=82=D1=80=D0=B8=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=BA=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=202026-08-31=20=E2=80=94=20=D0=B7=D0=B2=D1=83?= =?UTF-8?q?=D0=BA=20=D0=BC=D0=B8=D0=B3=D0=B0=D0=BD=D0=B8=D1=8F,=20=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=8B=20=D0=B2=20=D0=B1=D1=8B=D1=81=D1=82=D1=80?= =?UTF-8?q?=D1=8B=D1=85=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B0=D1=85,=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BE=D1=81=D0=B0=20HP=20=D0=BF=D0=BE=D1=81?= =?UTF-8?q?=D0=BB=D0=B5=20=D1=80=D0=B5=D1=81=D1=82=D0=B0=D1=80=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разбор без правок кода; все три отмечены как задачи по решению пользователя. 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 Claude-Session: https://claude.ai/code/session_011MsUsEFAQfsjjQpJ7RtKVY --- applications/SprPoP/docs/BUGS_OPEN.md | 39 +++++++++++ applications/SprPoP/docs/TASKS_OPEN.md | 93 ++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/applications/SprPoP/docs/BUGS_OPEN.md b/applications/SprPoP/docs/BUGS_OPEN.md index 8f3abe3..356e6bf 100644 --- a/applications/SprPoP/docs/BUGS_OPEN.md +++ b/applications/SprPoP/docs/BUGS_OPEN.md @@ -39,6 +39,7 @@ | [PV-RENDER-BOUND](#pv-render-bound) | сцена с принцессой рисуется дороже бюджета: ~49 тиков/с вместо 60, музыка уезжает от картинки | производительность | **исправлено 2026-08-28**: кадр разложен на блоки по интервалу, удешевлять не понадобилось | | [MUS-LEFT-TEAR](#mus-left-tear) | `pop_mus_left` (16 бит, пишет прерывание) читается из главного цикла неатомарно — возможен ложный «трек кончился» | **потенциальный** | открыт: хазард показан рассуждением, в прогоне не проявился | | [CLIMB-VS-GUARD](#climb-vs-guard) | Кид подтягивается к стражу этажом выше: у нас удар засчитывается и убивает, в оригинале Кид просто срывается без урона; страж при этом способен провалиться сквозь пол вслед за Кидом | бой/физика | открыт: цепочка удара сверена — совпадает, расходятся входные данные (2026-08-31) | +| [HP-BAR-RESTART](#hp-bar-restart) | после гибели и Ctrl+A на ОДНОЙ из двух страниц остаётся полоса HP по результатам боя | дабл-буфер/UI | открыт: разбор есть (2026-08-31) | --- @@ -1394,3 +1395,41 @@ uint16_t pop_music_left(void) { uint16_t a, b; **Решение пользователя:** отложено на будущее (2026-08-31) — поведение в этой связке расходится широко, чинить нужно целиком, а не по одному симптому. + + +## HP-BAR-RESTART + +**Симптом (пользователь, 2026-08-31).** Гибель на 2-м уровне, возврат к +началу по Ctrl+A: строка HP на одном из двух экранов остаётся отрисованной +по результатам боя. Замечание там же: «в начале уровня она отрисовывается +только в один экран». + +**Что уже известно.** + +Ctrl+A у нас — РЕСТАРТ УРОВНЯ (`sprpop_cold.c`, тот же путь, что пункт меню +`POP_MENU_RESTART_LEVEL`), а не возврат в заставку. + +Перерисовка полосы устроена счётчиком страниц, а не флагом: +`pop_hp_invalidate` ставит `hp_todo = 2`, и `pop_hp_draw` тратит по одной +странице за кадр (`src/pop_cdraw.c`). Все четыре холодных пути +(старт уровня, вход в комнату, быстрая загрузка, возврат из меню) +инвалидацию зовут — механизм на месте. + +**Главный подозреваемый — зона статус-строки.** Полоса HP делит строку со +статус-текстом, и пока текст висит (`pop_status_ticks != 0`), стирание +чистит ТОЛЬКО края — левее `POP_STATUS_L` и правее `POP_STATUS_R`, — +а середину не трогает, чтобы текст не мигал. При старте уровня текст как +раз висит («LEVEL 2»), поэтому всё, что от прошлой полосы попало в +середину, там и остаётся. Полоса стража рисуется справа налево от 314 и +при большом запасе HP заходит именно в эту незачищаемую зону. + +**Что проверить при взятии в работу.** + +1. Значения `POP_STATUS_L`/`POP_STATUS_R` против реальной ширины полос: + при скольких делениях полоса стража (или Кида) заходит под текст. +2. Уходят ли оба прохода `hp_todo` в РАЗНЫЕ страницы на старте уровня — + если между ними нет переворота, обе перерисовки лягут в одну. +3. Возможное решение: на старте уровня чистить полосу во всю ширину + независимо от статус-текста (текст всё равно перерисовывается заново + через `pop_status_invalidate`), либо запоминать максимальную ширину + прошлой полосы и стирать по ней. diff --git a/applications/SprPoP/docs/TASKS_OPEN.md b/applications/SprPoP/docs/TASKS_OPEN.md index 4ff44bb..eb99bc0 100644 --- a/applications/SprPoP/docs/TASKS_OPEN.md +++ b/applications/SprPoP/docs/TASKS_OPEN.md @@ -1009,6 +1009,99 @@ tp/10 у факелов таблицей, пустой слот соперник ## P1 — берётся в любой момент +### TIME-SPEED. Часы бегут быстрее в FAST/FASTEST + +**Наблюдение (пользователь, 2026-08-31):** «при режиме FAST/FASTEST время +начинает бежать быстрее — похоже, время мы считаем в наших логических +кадрах, и если они отрисовываются чаще, то и время быстрее». + +Так и есть. `pop_timer_tick` (`src/pop_timer.c`) уменьшает счётчик на +КАЖДОМ логическом кадре — ровно как оригинал. Но длина логического кадра +у нас зависит от режима скорости (`src/pop_pace.h`): + +| режим | обычный кадр | бой | +|---|---|---| +| NORMAL | 4 кадра луча (81,9 мс) | 5 (102,4 мс) | +| FAST | 3 (61,4 мс) | 4 (81,9 мс) | +| FASTEST | 3 (61,4 мс) | 3 (61,4 мс) | + +Разная длина кадра в игре и в бою — ПОВЕДЕНИЕ ОРИГИНАЛА (подтверждено +пользователем), и часы, идущие в бою медленнее, трогать не нужно. +Расхождение только в наших добавочных режимах: NORMAL повторяет оригинал, +а FAST/FASTEST ускоряют всё разом, включая ход часов — минута игрового +времени проходит примерно на треть быстрее реальной. + +**Варианты.** + +1. Оставить как есть: быстрый режим ускоряет игру целиком, это честно и + предсказуемо. Ноль работы и ноль риска. +2. Развязать часы от темпа: тикать не по логическому кадру, а по + накопленным кадрам ЛУЧА (4 кадра луча = 1 тик). Тогда минута остаётся + минутой в любом режиме, а в бою часы по-прежнему замедляются, как в + оригинале. Цена — счётчик-накопитель в `pop_timer_tick`. + +**Решать пользователю; по умолчанию — вариант 1** (режимы скорости +отладочные, а вариант 2 разводит наш таймер с оригинальным на уровне +механики). + + +### SND-SPEAKER-38. Звук мигания надписи: синтезировать ноты спикера в PCM + +**Постановка (пользователь, 2026-08-31):** «когда надпись Press Button +начинает мигать — в SDLPoP воспроизводится звук на каждое моргание, у нас +тишина». Решение выбрано там же: «проще синтезировать как PCM и добавить +в наш SND атлас». + +**Наш код НЕ виноват и правки не требует.** `pop_dead_prompt` +(`src/pop_status.c`) уже зовёт звук 38 на каждом появлении строки — ровно +как оригинал. Пусто в НАБОРЕ: в `assets/packed/SND/snd.idx` слот 38 имеет +длину 0, играть нечего. + +**Почему его нет.** У оригинала три параллельных набора звука, и номера +разложены по ним не подряд: + +| набор | что | номера | у нас | +|---|---|---|---| +| `DIGISND1..3.DAT` | оцифровка | 0–23, 44–49, 51 | берём, это и есть наш SND | +| `MIDISND1..2.DAT` | мелодии | 24–30, 32, 33, 35–37, 39–41, 43, 50, 52–56 | берём отдельно, как музыку в `MUS/` | +| `IBM_SND1..2` | ноты PC-спикера | 0–56 (весь диапазон) | НЕ берём вовсе | + +Звук 38 есть ТОЛЬКО в наборе спикера — ни оцифровки, ни мелодии для него +не существует, поэтому он и провалился между двумя нашими конвейерами. + +**Полная ревизия недостающего (сделана 2026-08-31).** Номера, которых нет +ни в оцифровке, ни среди мелодий: **31, 34, 38, 42**. Из них 31, 34 и 42 — +пустые заглушки в один байт, нот внутри нет. **Реально звучит ровно один +номер — 38.** То есть задача закрывает единственную дыру в наборе, а не +открывает семейство. + +**Формат ресурса** (канон — `docs/PoP/POP-DAT-FormatSpecifications.pdf`, +раздел «Internal PC Speaker»): заголовок 3 байта, из них байт 1 — темп в +долях на две секунды; далее тройки «частота в герцах (2 байта) + длина в +долях (1 байт)», нулевая частота = пауза; в конце маркер `12 00`. + +Звук 38 (`IBM_SND1/res10038.bin`, 17 байт) — нисходящий сигнал из четырёх +нот: 2500, 2000, 1500, 1000 Гц по одной доле, темп 72 → около 110 мс. +Для сверки разбора: звук 17 (мягкое приземление) — одна нота 49 Гц на три +доли. + +**Что делать.** + +1. В `tools/pop_pack_sound.py` — генератор PCM из нот: меандр на нашей + частоте вывода (`RATE`), амплитуда умеренная (сигнал короткий и резкий, + полный размах будет колоть ухо), пауза = уровень тишины `0x80`. +2. Брать из спикера ТОЛЬКО те номера, которых нет ни в оцифровке, ни в + мелодиях — сейчас это ровно 38. Правило важнее списка: если брать всё + подряд, синтез перекроет собой мелодии, которые мы играем из `MUS/`. +3. Дальше всё уже готово: звук ложится в атлас и индекс общим путём, + `snd.idx` пересобирается, EXE не меняется (раскладка читается с диска). +4. Проверка: `make resources` → слот 38 в индексе получил ненулевую длину; + в MAME дождаться мигания надписи после смерти — должен звучать сигнал. + +**Цена.** Один короткий звук в наборе (~2 КБ после выравнивания на блок), +кода в игре — ноль. + + ### PERF-SWEEP. Поиск узких мест по ВСЕЙ игре, а не в одной сцене **Постановка (пользователь, 2026-08-18):** «пока мы тестируем на регресс