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>
This commit is contained in:
@@ -383,3 +383,73 @@ IBM PC 1989 года: один квадратный голос. Он у нас
|
||||
DOS-версией; у нас последовательности разведены по доменам
|
||||
(`impl_diff.md`), поэтому у тряски свой сид, а холостой бросок не делаем —
|
||||
на розыгрыши физики и кладки это не влияет.
|
||||
|
||||
## 9. Цена звука в тактах (замер 2026-08-20, MAME)
|
||||
|
||||
Вопрос был поставлен так: звук идёт по прерываниям, значит размазан по всем
|
||||
фазам кадра — и если фазы укладываются, всё хорошо? Да, но проверять это
|
||||
надо не по фазам, а по двум числам, потому что **нагрузка от звука
|
||||
постоянная и от сцены не зависит вовсе**.
|
||||
|
||||
### 9.1 Одно прерывание CBL
|
||||
|
||||
Зонды: `bpset` на входе трамплина (`_irq_tramp`) и на `reti` ветки CBL,
|
||||
разница `totalcycles`. 398 замеров в сцене 11/15.
|
||||
|
||||
| величина | значение |
|
||||
|---|---:|
|
||||
| цена одного прерывания | **7 825 тактов ровно**, с разбросом до 8 299 (среднее 8 001) |
|
||||
| период между прерываниями | 245 759 тактов (= 128 сэмплов на 10 937,5 Гц) |
|
||||
| **доля процессорного времени** | **8 001 / 245 759 = 3,26 %** |
|
||||
|
||||
Цена постоянная, потому что работа фиксированная: OTIR ровно 128 байт плюс
|
||||
скобка сохранения контекста. Ветвлений по данным в насосе нет.
|
||||
|
||||
### 9.2 Дрожание обслуживания — риск для ЗВУКА, не для кадра
|
||||
|
||||
Период плавает 228 804 … 262 734, то есть прерывание опаздывает максимум на
|
||||
**~17 000 тактов = 0,8 мс**. Это самая длинная DI-скобка в коде
|
||||
(акселератор режется по 16 строк, memory `sprinter_wait_states_2x`).
|
||||
Буфер CBL — 128 сэмплов = **11,7 мс**, запас **14×**. Недолива быть не
|
||||
может; счётчик `cbl_underruns()` это подтверждает косвенно (наш `fill`
|
||||
всегда возвращает 1, поэтому он ловит только отсутствие данных, не
|
||||
опоздание).
|
||||
|
||||
### 9.3 A/B в одном прогоне (Ctrl+S), сцена 11/15
|
||||
|
||||
Один и тот же кадр, звук выключается на ходу — сравнение чистое.
|
||||
|
||||
| | работа min | работа max | работа avg | прерываний CBL на кадр |
|
||||
|---|---:|---:|---:|---:|
|
||||
| звук ВКЛ | 453 132 | 572 532 | **498 064** | 1,40 |
|
||||
| звук ВЫКЛ | 444 348 | 559 434 | **487 372** | 0,00 |
|
||||
| разница | +8 784 | +13 098 | **+10 692 (+2,2 %)** | |
|
||||
|
||||
Разница на лёгком кадре (+8 784) — ровно одно прерывание, сходится с §9.1.
|
||||
`CBL/кадр = 0` при выключенном звуке подтверждает, что Ctrl+S реально
|
||||
ЗАКРЫВАЕТ CBL, а не глушит сэмпл: иначе насос продолжал бы отдавать блоки
|
||||
тишины и платить те же 3,26 %.
|
||||
|
||||
### 9.4 Укладываемся ли
|
||||
|
||||
Логический кадр (`pop_pace.h`): NORMAL = 4 растра вне боя = **1 720 000
|
||||
тактов**, FASTEST = 3 растра = **1 290 000**.
|
||||
|
||||
| | работа | доля NORMAL | доля FASTEST |
|
||||
|---|---:|---:|---:|
|
||||
| 11/15, обычная позиция | 498 064 | 29 % | 39 % |
|
||||
| 11/15, тяжёлая позиция (Кид на 7 px правее) | 750 066 макс | 44 % | 58 % |
|
||||
|
||||
Период кадра за все прогоны: 1 719 936 … 1 720 752 — ровно 4 растра, ни
|
||||
одного проскока. **Звук занимает 1,1 % бюджета NORMAL и 1,5 % FASTEST.**
|
||||
|
||||
### 9.5 Где 3,26 % МОГЛИ БЫ стоить дорого
|
||||
|
||||
Ответ «всё размазано, если фазы влезли — ок» верен с одной оговоркой.
|
||||
Пейсинг квантован растром: работа 1,00 растра и 1,02 растра дают РАЗНЫЙ
|
||||
период кадра (3 против 4 интервалов), то есть скачок сразу на 20 мс.
|
||||
Значит звук опасен ровно в одной ситуации — когда сцена стоит в пределах
|
||||
~8 000 тактов НИЖЕ кратного растру порога. Сейчас ближайший запас — 540 000
|
||||
тактов до порога FASTEST, то есть в 60 раз больше цены звука. Проверять
|
||||
эту оговорку заново стоит только если работа кадра подберётся к 430 000 или
|
||||
860 000 вплотную.
|
||||
|
||||
@@ -60,8 +60,9 @@ Kid — `kid_data.h` (генерится `pop_extract_kid_data.py`).
|
||||
дабл-буфер (в однобуфере видно баги рисования без мерцания-через-кадр);
|
||||
**1** — заморозить кадр, **2** — продолжить (разбор позы/окклюзии);
|
||||
**ESC** — выход. Читы (`pop_cheat.h`): **K** — убить стража, **I** —
|
||||
бессмертие, **S** — выдать меч, **Shift+L** — следующий уровень,
|
||||
**+/−** — обход комнат (`ROOMNAV`). Уровень грузится по номеру
|
||||
бессмертие, **Shift+L** — следующий уровень,
|
||||
**+/−** — обход комнат (`ROOMNAV`). Не чит, а штатная клавиша оригинала:
|
||||
**Ctrl+S** — вкл/выкл звук (пурпурная палочка в борте = звука нет). Уровень грузится по номеру
|
||||
(`pop_level_load_num`), на диске лежат все 15.
|
||||
|
||||
## Модули
|
||||
|
||||
@@ -22,7 +22,9 @@ extern uint8_t pop_cheats; /* 0 = выключены, иначе включ
|
||||
#define KBD_CHEAT_IMMO 0x43 /* I (PS/2 set 2) — бессмертие Кида по кругу
|
||||
* 0 -> 1 (только бой) -> 2 (и мелкий урон) -> 0;
|
||||
* уровни расписаны в pop_state.h */
|
||||
#define KBD_CHEAT_SWORD 0x1B /* S (PS/2 set 2) — выдать Киду меч */
|
||||
/* Ctrl+S (PS/2 set 2: S = 0x1B) — вкл/выкл звук; порт Ctrl+S из SDLPoP
|
||||
* (seg000:657). С Ctrl, а не голая S: у оригинала клавиша именно такая. */
|
||||
#define KBD_SOUND_TOGGLE 0x1B
|
||||
#define KBD_CHEAT_NEXTLVL 0x4B /* Shift+L (PS/2 set 2) — следующий уровень */
|
||||
#define KBD_CHEAT_XDEC 0x54 /* [ (PS/2 set 2) — сдвинуть Кида на 1 px влево */
|
||||
#define KBD_CHEAT_XINC 0x5B /* ] (PS/2 set 2) — сдвинуть Кида на 1 px вправо */
|
||||
|
||||
@@ -28,6 +28,18 @@
|
||||
uint8_t pop_snd_page[POP_SND_PAGES];
|
||||
uint8_t pop_snd_ok;
|
||||
|
||||
/* ХОЧЕТ ЛИ ЗВУКА ПОЛЬЗОВАТЕЛЬ (Ctrl+S) — отдельно от pop_snd_ok.
|
||||
*
|
||||
* Разделять обязательно: pop_snd_ok значит «CBL сейчас открыт», и его
|
||||
* гасит служебная пауза на время загрузки уровня. Если бы Ctrl+S правил
|
||||
* только его, то первая же смена уровня позвала бы pop_sfx_start() и
|
||||
* вернула звук, который пользователь выключил. Поэтому start уважает
|
||||
* этот флаг, а pause его не трогает.
|
||||
*
|
||||
* Живёт в резиденте, а не в банке: метку в борте рисует отладочный ярлык
|
||||
* каждый кадр, и тащить ради одного байта трамплин незачем. */
|
||||
uint8_t pop_snd_want;
|
||||
|
||||
/* Курсор проигрывания. volatile: пишет главный цикл, читает прерывание.
|
||||
* Всё в 8/16 бит — 32-битной арифметики на Z80 избегаем (см. memory
|
||||
* avoid_32bit_arith_z80); поэтому и в таблице смещение уже разобрано на
|
||||
|
||||
@@ -38,6 +38,17 @@ void pop_sfx_pause(void) __banked;
|
||||
/* Закрыть CBL и отпустить страницы; звать на выходе. */
|
||||
void pop_sfx_close(void) __banked;
|
||||
|
||||
/* Вкл/выкл звук по воле пользователя (Ctrl+S, порт SDLPoP seg000:657).
|
||||
* Правит pop_snd_want и приводит вывод в соответствие. */
|
||||
void pop_sfx_toggle(void) __banked;
|
||||
|
||||
/* ХОЧЕТ ЛИ ЗВУКА ПОЛЬЗОВАТЕЛЬ. Не то же самое, что «CBL открыт»: вывод
|
||||
* ещё гасит служебная пауза на время загрузки уровня. Читать для
|
||||
* индикации; писать — только через pop_sfx_toggle. 0 и в случае, когда
|
||||
* набор вообще не поднялся (нет файлов, нет страниц) — то есть флаг
|
||||
* честно means «звука не будет». */
|
||||
extern uint8_t pop_snd_want;
|
||||
|
||||
/* Начать эффект с номером id (0..56 — нумерация оригинала, см.
|
||||
* sound_plan.md). Перебивает играющий. У звуков без оцифровки (это
|
||||
* музыка) длина 0 — вызов просто глушит текущий.
|
||||
|
||||
@@ -32,6 +32,7 @@ int pop_sfx_init(void) __banked
|
||||
}
|
||||
}
|
||||
snd_blk = blk;
|
||||
pop_snd_want = 1; /* набор есть — звук по умолчанию включён */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -52,8 +53,9 @@ int pop_sfx_init(void) __banked
|
||||
* резиденте W2 тесная, и её отказ ронял бы весь звук. */
|
||||
int pop_sfx_start(void) __banked
|
||||
{
|
||||
if (pop_snd_ok) return 0;
|
||||
if (!snd_blk) return -1; /* набор не загружен */
|
||||
if (pop_snd_ok) return 0;
|
||||
if (!pop_snd_want) return 0; /* выключено пользователем (Ctrl+S) */
|
||||
if (!snd_blk) return -1; /* набор не загружен */
|
||||
if (cbl_open(CBL_FREQ_10K9, CBL_FMT_MONO8, CBL_PUMP_OTIR,
|
||||
CBL_UNDERRUN_APP, pop_sfx_fill) != 0)
|
||||
return -1;
|
||||
@@ -74,3 +76,22 @@ void pop_sfx_close(void) __banked
|
||||
pop_sfx_pause();
|
||||
if (snd_blk) { mem_free_block(snd_blk); snd_blk = 0; }
|
||||
}
|
||||
|
||||
/* Ctrl+S — порт `turn_sound_on_off((!is_sound_on) * 15)` из SDLPoP
|
||||
* (seg000:657). Выключение реально ЗАКРЫВАЕТ CBL, а не глушит сэмпл:
|
||||
* иначе насос продолжал бы отдавать блоки тишины 85 раз в секунду, а это
|
||||
* 3,3 % процессорного времени ни за что (замер: 8 001 такт на прерывание
|
||||
* при периоде 245 759). Открытие обратно дешёвое — cbl_open только
|
||||
* настраивает железо, страницы набора никуда не девались. */
|
||||
void pop_sfx_toggle(void) __banked
|
||||
{
|
||||
if (pop_snd_want) {
|
||||
pop_sfx_pause(); /* pause сам не трогает want... */
|
||||
pop_snd_want = 0; /* ...поэтому гасим его следом */
|
||||
} else {
|
||||
pop_snd_want = 1;
|
||||
/* Набора может не быть вовсе (файлы не прочитались, страницы не
|
||||
* дались) — тогда откатываем флаг, чтобы индикация не врала. */
|
||||
if (pop_sfx_start() != 0) pop_snd_want = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked
|
||||
* Флаги «фронта» стали статиками модуля — раньше они были локалями main.
|
||||
* Смысл прежний: одно нажатие = одно срабатывание.
|
||||
*/
|
||||
static uint8_t cheat_prev, immo_prev, sword_prev, nlvl_prev;
|
||||
static uint8_t cheat_prev, immo_prev, sound_prev, nlvl_prev;
|
||||
static uint8_t upsd_prev, xdec_prev, xinc_prev;
|
||||
|
||||
void pop_cheat_tick(void) __banked
|
||||
@@ -328,8 +328,10 @@ void pop_cheat_tick(void) __banked
|
||||
* K — убить стража;
|
||||
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
|
||||
* иначе боёвку проверять получается ровно три раза);
|
||||
* S — выдать меч, не бегая за ним в комнату 15. Штатно меч
|
||||
* берётся с пола (pop_have_sword ставит подбор);
|
||||
* Ctrl+S — вкл/выкл звук (порт Ctrl+S из SDLPoP, seg000:657).
|
||||
* НЕ чит: работает и без pop_cheats, как в оригинале.
|
||||
* Ctrl обязателен — голая S у нас свободна, но менять
|
||||
* пользовательскую раскладку оригинала незачем;
|
||||
* Shift+L — следующий уровень (seg000:698, ветка next_level =
|
||||
* current_level + 1). Единственный вменяемый способ
|
||||
* тестировать уровни 2..15. NB: L без Shift — отладочный
|
||||
@@ -346,7 +348,8 @@ void pop_cheat_tick(void) __banked
|
||||
* следующего кадра. */
|
||||
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
||||
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
||||
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
|
||||
uint8_t s = (uint8_t)(kbd_raw_down(KBD_SOUND_TOGGLE) &&
|
||||
(kbd_raw_down(KBD_LCTRL) || kbd_raw_down(KBD_RCTRL)));
|
||||
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
|
||||
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
|
||||
uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE);
|
||||
@@ -357,7 +360,7 @@ void pop_cheat_tick(void) __banked
|
||||
* мелкий урон» -> 0. Что означают уровни — pop_state.h. */
|
||||
if (pop_cheats && i && !immo_prev)
|
||||
pop_immortal = (uint8_t)(pop_immortal >= 2 ? 0 : pop_immortal + 1);
|
||||
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
|
||||
if (s && !sound_prev) pop_sfx_toggle(); /* не под pop_cheats */
|
||||
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
|
||||
pop_next_level = (uint8_t)(pop_current_level + 1);
|
||||
/* U — переворот экрана (seg000:0793 — чит и в оригинале).
|
||||
@@ -370,7 +373,7 @@ void pop_cheat_tick(void) __banked
|
||||
if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1);
|
||||
cheat_prev = k;
|
||||
immo_prev = i;
|
||||
sword_prev = s;
|
||||
sound_prev = s;
|
||||
nlvl_prev = nl;
|
||||
upsd_prev = up;
|
||||
xdec_prev = xd;
|
||||
@@ -388,6 +391,11 @@ void pop_cheat_tick(void) __banked
|
||||
* белый x 100..175 комната (1..24)
|
||||
* голубой x 190..205 скорость (1 палочка NORMAL, 2 FAST, 3 FASTEST)
|
||||
* красный x 220..230 бессмертие (1 палочка = только бой, 2 = и мелкий урон)
|
||||
* пурпурный x 245 звук ВЫКЛЮЧЕН (Ctrl+S) либо не поднялся вовсе
|
||||
|
||||
* Звук показан «от противного» — палочка есть, когда звука НЕТ. Иначе
|
||||
* индикатор ничего не сообщает: включённый звук слышно и так, а вот
|
||||
* молчание неотличимо от «в этот момент нечему звучать».
|
||||
*
|
||||
* ЦВЕТА — индексы ИГРОВОЙ палитры, и выбраны они не по вкусу, а по двум
|
||||
* ограничениям.
|
||||
@@ -413,6 +421,7 @@ static uint8_t room_label_sig = 0xFF; /* последняя показанна
|
||||
static uint8_t lvl_label_sig = 0xFF; /* ...уровень */
|
||||
static uint8_t spd_label_sig = 0xFF; /* ...режим скорости */
|
||||
static uint8_t imm_label_sig = 0xFF; /* ...бессмертие */
|
||||
static uint8_t snd_label_sig = 0xFF; /* ...звук (pop_snd_want) */
|
||||
static uint8_t room_label_redraw; /* остаток кадров (2 = обе страницы) */
|
||||
|
||||
/* Группа палочек: n штук от x вправо, шаг 5, ширина 2. */
|
||||
@@ -428,17 +437,21 @@ void pop_dbg_room_label(uint8_t room) __banked
|
||||
* (`if (n != g) g = n;` пишет разность вместо значения). */
|
||||
uint8_t prev_room = room_label_sig, prev_lvl = lvl_label_sig;
|
||||
uint8_t prev_spd = spd_label_sig, prev_imm = imm_label_sig;
|
||||
uint8_t prev_snd = snd_label_sig;
|
||||
uint8_t lvl = pop_current_level, spd = pop_speed_mode, imm = pop_immortal;
|
||||
uint8_t snd = pop_snd_want;
|
||||
room_label_sig = room;
|
||||
lvl_label_sig = lvl;
|
||||
spd_label_sig = spd;
|
||||
imm_label_sig = imm;
|
||||
if (room != prev_room || lvl != prev_lvl || spd != prev_spd || imm != prev_imm)
|
||||
snd_label_sig = snd;
|
||||
if (room != prev_room || lvl != prev_lvl || spd != prev_spd ||
|
||||
imm != prev_imm || snd != prev_snd)
|
||||
room_label_redraw = 2;
|
||||
if (!room_label_redraw) return;
|
||||
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(10, 0, 230, 5);
|
||||
bar(10, 0, 250, 5);
|
||||
|
||||
setfillstyle(SOLID_FILL, 0x3E); /* уровень — жёлтый */
|
||||
label_bars(10, (uint8_t)(lvl / 10));
|
||||
@@ -457,6 +470,10 @@ void pop_dbg_room_label(uint8_t room) __banked
|
||||
setfillstyle(SOLID_FILL, 0x3C);
|
||||
label_bars(220, imm); /* палочек = уровень (1 или 2) */
|
||||
}
|
||||
if (!snd) { /* звука НЕТ — пурпурный */
|
||||
setfillstyle(SOLID_FILL, 0x3D);
|
||||
label_bars(245, 1);
|
||||
}
|
||||
room_label_redraw--;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user