Статус: пакеты A–E ВЫПОЛНЕНЫ 2026-07-26 (скоуп «делаем всё», решение Макса). Ждёт приёмки на слух — что слушать, см. §8. Разделы 1–2 описывают состояние ДО работы и оставлены как объяснение, почему всё сделано именно так.


Аудит звукового слоя на 2026-07-26. Раунд 1 (Planning - SFX (FMOD)) поднял каркас: фасад, резолвер, каталог, 31 FMOD-событие, банки. Этот документ фиксирует, что из раунда 1 осталось недоделанным, где игра немая, и как привести SFX к одному уровню громкости. Контур — xgaida-x-nixi-audio.


1. Что звучит сегодня (факты, не память)

СлойСостояние
FMOD-события31 (event:/SFX/Combat/*, Relics/*, Effects/*, Feel/*, UI/*, event:/Stingers/*)
Исходных сэмплов58 (Kenney impact-sounds .ogg + RPG Essentials Free .wav)
AudioCatalog.asset16 точных ключей + 13 per-action дефолтов
Реально играеттолько бой: hit / shield / death / heal / evade / attack / fire / cast + victory/defeat/kill-стингеры
Вне бояодин вызов LoadoutViewModel.cs:67 (и тот мёртвый, см. §2.6)
Музыка / амбиентнет вообще
БанкиMaster.bank, Master.strings.bank, SFX.bank от 2026-07-13, консистентны с проектом

Пайплайн раунда 1 живой и воспроизводимый: манифест → FMOD Project/Tooling/populate.jsfmodstudiocl -scriptfmodstudiocl -build. FMOD Studio 2.03.14 установлен локально.


2. Диагностика: что сломано или недоделано

2.1. Под-шин нет — слайдеры «Музыка» и «Звук» ничего не делают (P0)

FmodAudioService пишет громкость в bus:/Music и bus:/SFX (FmodAudioService.cs:34-36). В FMOD-проекте таких шин не существует: есть три группы SFX_Combat, SFX_UI, Stingers — и все 31 событие роутятся мимо них, прямо в Master Bus (mixerInput.output → MixerMaster в каждом Metadata/Event/*.xml).

Следствие: работает только слайдер «Общий»; «Музыка» и «Звук» — тихий no-op (SetBusVolume глотает невалидную шину). Решение audio-subbuses («под-шины заложить рано») формально принято, фактически не выполнено.

2.2. Разброс громкости 19.3 dB (P0)

Замер всех 58 исходников (ffmpeg: RMS активной части, порог тишины −45 dB):

ПакRMS активной частиПик
Kenney impact-sounds−15.1 … −24.7 dB−0.8 … −1.4 dB (пик-нормализованы)
RPG Essentials Free−23.0 … −34.4 dB−3.7 … −19.7 dB

Разброс 19.3 dB между самым громким (impactPunch_medium_001) и самым тихим (56_Attack_03). Ни на одном событии нет volume-фейдера (все на 0 dB), так что разброс идёт на выход как есть. Хуже: внутри одного мульти-инструмента лежат сэмплы из обоих паков — event:/SFX/Combat/hit содержит Kenney (−15 dB) и RPG (−27…−31 dB), то есть один и тот же удар звучит то в полный голос, то вдвое тише. То же в shield, death, attack.

2.3. Глобального параметра TimeScale в FMOD-проекте нет (P1)

TimeScaleService.cs:208 исправно шлёт AudioParameters.TimeScale (0.05…3) в RuntimeManager.StudioSystem.setParameterByName. В проекте FMOD нет ни одного параметра (Metadata/ParameterPreset/ пуст), автоматизации питча тоже нет. Слоумо на добивании и скорости 2x/3x сейчас не слышны — звук идёт как при 1x.

2.4. Нет рандомизации и анти-каши (P1)

populate.js умеет только события, мульти-инструменты, банк и maxVoices. Категории в манифесте (pitchSemitones, volumeDb, cooldownMs, stealing, priority) объявлены, но не применяются — в скрипте это помечено как «GUI PASS». В метаданных нет ни одного модулятора. Следствие: «пулемётный» повтор одинакового сэмпла в плотном бою, никакого кулдауна и разумного voice stealing (только maxVoices).

2.5. Звук существует только внутри боя (P0 для покрытия)

AudioPresenter зарегистрирован в CombatLifetimeScope.cs:69 (Scoped) и умирает вместе с BattleScene. WorldLifetimeScope аудио не регистрирует. Меню, карта акта, награда, магазин, привал, инвентарь, переходы — немые полностью. IAudioService при этом синглтон в Root (RootLifetimeScope.cs:74), то есть шов для root-презентера уже есть.

2.6. Мёртвые ключи и хуки

ЧтоГдеПочему мёртвое
{relicId}.selectLoadoutViewModel.cs:67select — не AudioAction, фолбэка на дефолт нет → всегда тишина
onCardSelectSoundRewardScreenView.cs:36,109MenuRouter.cs:613-623 параметр не передаёт
ui.pause.ui, ui.resume.ui, ui.deploy_place.uiкаталог + FMODсобытия созданы, никто их не играет
AudioAction.Ui, Apply, Expire, Tickenum + резолвер + дефолтыни одного вызова в коде
kill-стингерAudioPresenter.cs:70без кулдауна (в отличие от CombatFeelDirector.KillSlowCooldown) — пачка добиваний наложится сама на себя
IAudioService.StopFmodAudioService.cs:28-32no-op, хендлы не хранятся → лупы/музыка/каналинг технически невозможны

2.7. Пайплайн не воспроизводится на другой машине (P2)

manifest.sourceRoot = C:/My Program Files/_TEMP/SFX — папка вне репозитория. При этом паки уже лежат в репо: Assets/Kenney/kenney_impact-sounds, kenney_interface-sounds (100 файлов UI, CC0), RPG_Essentials_Free. Правильный дом для отобранного и нормализованного материала — FMOD Project/SourceAudio/ (§3.3).


3. Громкость: метод и целевые числа

3.1. Целевой уровень

−23 dB RMS активной части, true peak ≤ −1 dBFS. Активная часть = сигнал выше −45 dB (для one-shot интегральный LUFS не считается — гейтинг EBU R128 требует материала длиннее 0.4 с, а у нас половина сэмплов короче).

Почему −23: при этой цели пиковый потолок ограничивает всего 4 файла из 58, а разброс воспринимаемой громкости схлопывается 19.3 dB → 1.6 dB. Более громкие цели упираются в пик-нормализованный Kenney (−20 → 18 ограниченных файлов, разброс 4.6 dB). Формула гейна: gain = min(target − rms, −1 − peak) — громкость никогда не покупается клиппингом.

Число совпадает с вещательным ориентиром EBU R128 (−23 LUFS) не по необходимости, а по удобству: запас до 0 dBFS остаётся микшеру, а не сэмплам.

3.2. Слои громкости (кто за что отвечает)

  1. Сэмпл — нормализован к −23 dB (одинаковый старт для всех, §3.1).
  2. Событие FMOD — художественный offset (тычка тише ульты), крутится по слуху.
  3. Категория/под-шина — баланс групп (§3.4).
  4. Слайдеры игрокаbus:/, bus:/Music, bus:/SFX.

Свой C#-инструмент сведения не строим — это решение audio-subbuses, микс живёт в FMOD.

3.3. Где живут исходники

FMOD Project/SourceAudio/{category}/{event}_{NN}.wav — нормализованные копии в репо, manifest.sourceRoot указывает на них. Тогда populate воспроизводится на любой машине, а пере-нормализация — один прогон скрипта.

3.4. Стартовые уровни шин и категорий (для Live Update)

bus:/                    0 dB
├── bus:/Music          −6 dB   (задел, пока пусто)
└── bus:/SFX             0 dB
    ├── SFX/Combat       0 dB   ← якорь микса (боевой импакт)
    ├── SFX/UI          −5 dB   (UI не должен спорить с боем)
    ├── SFX/Ambient     −9 dB   (задел)
    └── SFX/Stingers    +1 dB, sidechain-дак Combat на −5 dB

Категорийные offset’ы поверх нормализованного сэмпла: impact 0, whoosh −2, cast −1, tonal −3, death 0, ui −2, stinger 0.

3.5. Рандомизация (анти-«пулемёт»)

КатегорияPitchVolume
impact / death±2.5 st−3 dB
whoosh±1.5 st−3 dB
tonal / cast±0.5…1 st−2 dB
ui±1 st−2 dB
stinger00

(Random-модулятор громкости в FMOD всегда отнимает — потому цель нормализации берётся с запасом на этот минус.)

3.6. Слоумо

Параметр TimeScale (0.05…3, дефолт 1) → автоматизация питча SFX/Combat по 12·log2(ts) (0 st при ts=1). Музыка, UI и стингеры параметром не трогаются.


4. Реестр недостающих звуков

Ключи — по канону {contentId}.{action}. Приоритет: P0 — без этого игра звучит сломанной, P1 — заметная дыра, P2 — полировка. Колонка «материал» — что взять из уже лежащего в репо (Kenney interface-sounds ki, Kenney impact-sounds kimp, RPG Essentials rpg, Sonniss GDC son).

4.1. Бой: то, чего нет в симуляции (нужны швы, §5)

КлючМоментПриМатериал
battle.start.stingerстарт боя (событие есть, вызова нет)P0son Horn Braams / rpg 55_Encounter
effect.{id}.applyналожен стан/заморозка/щит/яд/баф/стелсP0rpg 8_Buffs_Heals, son Magic
effect.{id}.expireэффект спалP1то же, затухающее
effect.{id}.tickтик DoT/HoT (сейчас звучит как обычный hit)P1rpg 46_Poison
combat.unit_spawn.uiюнит появился на аренеP1ki pluck / son Organic UI
combat.attack_interrupted.evadeзамах сорван станомP2ki scratch
combat.knockback_start.attackюнита отбросилоP1kimp whoosh-слой
combat.knockback_land.hitприземление после полётаP1kimp impactWood_heavy
combat.projectile_miss.evadeснаряд ушёл в никудаP2ki swish
combat.aoe.castзона удара сработала (AreaHit)P2son Elemental Impacts
combat.step.uiшаги (хук contact-dust уже с кулдауном)P2kimp footstep_*
battle.reset.stingerбой перезапущен (dev-R) — нужен и как глушилка хвостовP2

4.2. Feel-слой (визуал уже есть, звука нет)

КлючМоментПриМатериал
feel.death_shatter.deathразлёт на осколки (UnitView.cs:1032) — самый громкий кадр и он немойP0kimp impactGlass_heavy (событие уже создано!)
feel.heavy_hit.hitудар выше HeavyHitFracP0kimp impactPunch_heavy (событие есть)
feel.finisher.stingerвход в финишер-слоумоP1son Bass Downers
feel.death_anticipate.tickдрожь перед разлётомP2son low rumble

feel.kill.stinger уже играет — но нужен кулдаун (§2.6).

4.3. Расстановка отряда

КлючМоментПриМатериал
ui.deploy_grab.uiвзял юнитаP0ki drop_001 (обратный) / rpg 070_Equip
ui.deploy_place.uiпоставил (событие есть, вызова нет)P0rpg 070_Equip
ui.deploy_reject.uiневалидная клетка (ветки нет в коде)P0ki error_00X
ui.deploy_valid.uiпризрак вошёл в валидную зону (edge)P2ki tick_00X, тихо
ui.relic_equip.uiмементо надето на юнитаP0rpg 070_Equip_10
ui.relic_unequip.uiмементо снятоP1rpg 071_Unequip_01
ui.deploy_ready.stingerнажали «Начать»P0ki confirmation

4.4. Карта акта (сейчас немая целиком)

КлючМоментПриМатериал
map.node_hover.uiнаведение на узелP0rpg 001_Hover_01
map.node_select.uiвыбран доступный узелP0ki select_00X
map.node_locked.uiклик по недоступному (nudge уже есть)P0ki error / rpg 033_Denied
map.travel_start.uiфишка отряда поехалаP1ki scratch, тихо
map.travel_arrive.uiприехала в узелP1ki bong
map.reveal.uiоткрылись новые узлы / разошлась дымкаP1son Organic UI
map.open.ui / map.close.uiкарта показана / убранаP1ki open / close
map.path_dot.tickбегущая волна по дорожке (по темпу)P2ki tick, очень тихо

4.5. Экраны забега и переходы

КлючМоментПриМатериал
flow.fade_in.ui / flow.fade_out.uiшторка-чернила закрылась/открыласьP0son Ultra Transitions
flow.node_enter.stingerвход в узел (по типу узла)P1son Transitions
screen.open.ui / screen.close.uiлюбой Push/Pop экрана (один шов)P0ki open_00X / close_00X
screen.modal_open.uiмодалка поверхP1ki maximize
reward.open.stingerоткрылся экран наградыP0rpg 013_Confirm + son fanfare
reward.card_select.uiклик по карточке награды (хук мёртв)P0ki select
reward.take.stingerмементо взятоP0rpg 079_Buy_sell / son Magic
reward.skip.uiнаграда пропущенаP1ki back
run.gold_gain.uiначислено золотоP0ki coin / rpg 079_Buy_sell
shop.buy.ui / shop.sell.ui / shop.reroll.uiпокупка / продажа / рероллP1rpg 079_Buy_sell, ki switch
shop.denied.uiне хватает золота / нет местаP1rpg 033_Denied
chest.open.stingerкрышка сундука откинуласьP1kimp impactWood_heavy + son
camp.action.ui / camp.denied.uiдействие привала / не по кармануP1ki confirmation / error
event.choice.uiвыбран вариант текстового ивентаP1ki select
event.result.uiпоявился текст последствияP2ki pluck
run.outcome_victory.stinger / run.outcome_defeat.stingerэкран исхода забегаP0son Colossal / Bass Downers
run.start.stingerзабег начался (отряд встал в мир)P1son Horn
menu.title_card.stingerтайтл-кардP1son Transitions
menu.show.ui / menu.hide.uiглавное меню появилось/ушлоP1ki open / close

4.6. UI-мелочь (один шов на корне панели, §5.2)

КлючМоментПриМатериал
ui.click.uiлюбая кнопкаP0ki click_00X
ui.hover.uiнаведение на интерактивP0rpg 001_Hover_01, очень тихо
ui.tab.uiпереключение таба/режима топбараP0ki switch_00X
ui.toggle.uiтумблер настроекP1ki toggle_00X
ui.slider.uiшаг слайдера (по отпусканию, не покадрово)P1ki tick_00X
ui.slider_test.uiтестовый бип на слайдере «Звук»P1дефолт ui
ui.back.ui«Назад»/«Отмена»/ESCP0ki back_00X
ui.disabled.uiклик по недоступному действиюP1ki error_00X
ui.tooltip_show.uiтултип раскрылсяP1ki pluck, очень тихо
ui.tooltip_detail.uiShift → подробный режимP2ki scratch («шелест страницы»)
ui.drag_grab.ui / ui.drag_drop.ui / ui.drag_reject.uiдраг в UITK: взял / положил / мимоP0ki drop / error
ui.scroll.uiпрокрутка списка (колбэков ещё нет)P2ki scroll_00X
ui.pause.ui / ui.resume.uiпауза боя (события есть, вызовов нет)P0уже в проекте
ui.speed.uiсмена скорости 1x/2x/3xP1ki tick
ui.camera.uiсмена режима камеры (Tab)P2ki swish
ui.quit.uiвыход из игрыP2ki close

4.7. Амбиент и музыка (требует хранимых EventInstance, §5.4)

КлючМоментПри
music.menu.loopглавное менюP1
music.map.loopкарта актаP1
music.battle.loopбой (со слоями по интенсивности — backlog-audio-sfx)P1
ambient.arena.loopфон ареныP2
ambient.map.loopфон картыP2

Итого в реестре: ~85 ключей, из них P0 — 27.


5. Швов в коде не хватает

5.1. Симуляция (нужны новые события)

ШовГдеЗачем
EffectSystem.OnEffectApplied(unit, def, source)EffectSystem.cs:101 (+ стакинг :380, мгновенный :154)открывает весь слой статусов разом; def.Id уже под рукой
OnEffectExpired → передавать def, не только EffectTagEffectSystem.cs:33,331без id ключ effect.{id}.expire не собрать
DisplacementSystem: старт полётаDisplacementSystem.cs:50приземление уже есть (:107)
ProjectileSystem: промах/деспавнProjectileSystem.cs:81сейчас снаряд исчезает беззвучно
AreaHit → добавить источник/contentIdCombat/AreaHit.cs:11сейчас структура не несёт id, ключ не собрать

5.2. UI: один шов вместо сотни вызовов

UiSoundSystem.Attach(_doc.rootVisualElement) рядом с UiRootBootstrap.cs:208 (тем же приёмом, что TooltipSystem.Attach): ClickEvent / PointerEnterEvent на корне панели, разбор по USS-классу (.gm-button, .gm-chip, .gm-card, .gm-slot, .gm-tab). Озвучивает все клики и ховеры разом, без правок в экранах.

Второй уровень (если нужен разный звук): компонентные базы Chip, Slot, RelicCard, SliderRow, ToggleRow, UiDragDrop — по одному месту на семейство.

Открытие/закрытие экранов — UiNavigator.Push/Pop/RemoveScreen/PopAll (UiNavigator.cs:120,143,250,171), там же виден ScreenKind.

5.3. Флоу забега: root-презентер на MessagePipe

RunAudioPresenter в Root-скоупе, подписки на готовые события: OpenRewardRequest, OpenShopRequest, OpenChestRequest, OpenCampRequest, OpenTextEventRequest, OpenContinueRequest, OpenNodeFarewellRequest, OpenOutcomeRequest, OpenMainMenuRequest, MainMenuVisibilityChangedEvent, OpenTitleCardRequest, RunPartyReadyEvent, WorldMapSpaceChangedEvent, TestZoneChangedEvent, RelicDragEvent, ScreenFadeChangedEvent, BattleEndedEvent.

Плюс IBattleSession.PhaseChanged (BattleSession.cs:189) — один C#-event закрывает None → Deployment → Fighting → Interlude, то есть старт расстановки, старт боя, конец боя и передышку. Живёт в Root → переживает бой.

Карта акта и DeploymentController наружу ничего не шлют — там нужны локальные вызовы (hover/select/locked/grab/place/reject) либо новые события.

5.4. Фасад: лупы

IAudioService.Stop — no-op. Для музыки/амбиента нужен словарь key → EventInstance в FmodAudioService (Play с create+start, Stop с stop(ALLOWFADEOUT)+release). Без этого пункт 4.7 нереализуем.


6. Пакеты работ

ПакетСодержаниеИтог
A. Микснормализация исходников в SourceAudio (−23 dB / −1 dBTP), шины SFX+Music+под-шины, роутинг событий, категорийные offset’ы, рандом-модуляторы, cooldown/stealing, параметр TimeScale + питч-кривая, пересборка банковSFX на одном уровне, слайдеры работают, слоумо слышно
B. ШвыUiSoundSystem на корне панели, RunAudioPresenter на MessagePipe, PhaseChanged, OnEffectApplied, death-shatter/heavy-hit хуки, кулдаун kill-стингера, починка {relicId}.select и onCardSelectSoundигра перестаёт быть немой вне боя
C. Контентотбор материала под ~85 ключей, populate, наполнение каталога, voicing-матрица зелёнаязвучит всё из §4
D. ЛупыEventInstance-словарь, музыка меню/карты/боя, амбиентпоявляется музыка
E. Тестывалидация «каталог ↔ GUIDs банка», тест «у каждого действия есть дефолт», тест ключей-сиротрассинхрон ловится в CI

7. Что сделано (2026-07-26)

7.1. Пайплайн

scripts/audio/audio_map.py — единственный источник правды: ключ → категория → исходные сэмплы. Из него:

audio_map.py
  → build_source_audio.py   нормализация (-23 dB RMS / -1 dBTP) → FMOD Project/SourceAudio + manifest.json
  → build_populate.py       → FMOD Project/Tooling/populate.js
  → fmodstudiocl -script    шины, события, микс, параметр TimeScale
  → fmodstudiocl -build     банки → Assets/StreamingAssets
  → Alebardium/Audio/Populate Catalog from Manifest   → AudioCatalog.asset

SourceAudio — промежуточный артефакт (в .gitignore): FMOD при импорте кладёт свою копию в FMOD Project/Assets, и версионируется именно она. Музыкальное сырьё — FMOD Project/MusicSource.

Итог: 109 событий, 211 сэмплов, 95 точных ключей + 14 дефолтов, 0 пустых ссылок.

7.2. Микс

  • Шины bus:/SFX/{Combat,UI,Ambient,Stingers} + bus:/Music; события роутятся по категории. Слайдеры «Общий/Музыка/Звук» теперь доходят до звука.
  • Громкость: сэмплы выровнены, поверх — категорийные offset’ы (UI −2, ui_soft −9, tonal −3…), поверх — фейдеры шин (UI −5, Ambient −9, Stingers +1, Music −6).
  • Анти-каша: на каждом событии maxVoices + stealing + cooldown + priority по категории (удары 4/Quietest/50 мс/Low, касты 2/Virtualize, стингеры 1/None/Highest), плюс рандомизация питча (±0.5…2.5 st) и громкости (−2…−3 dB).
  • Слоумо: глобальный параметр TimeScale (0.05…3) с кривой питча на bus:/SFX/Combat.

7.3. Код

ШовГде
Клики/наведения/табы/тумблеры/слайдеры/скролл — один слушатель на корне панелиUI/UiSoundSystem.cs, привязка в UiRootBootstrap
Экраны, карта, шторка, исход, меню, музыка (root-скоуп, переживает бой)Game/Services/RunAudioPresenter.cs
Открытие/закрытие любого экранаUiNavigator.Push/Pop
Статусы: наложен/спал с id эффектаEffectSystem.OnEffectApplied / OnEffectEndedAudioPresenter
Килл-стингер (под кулдауном слоумо), тяжёлый удар, финишерCombatFeelDirector
Разлёт на осколкиUnitView.StartShatter
Узлы карты: наведение/выбор/недоступен/поездкаWorldMapView
Расстановка: взял/поставил/отказ, мементо надетоDeploymentController
Пауза, скоростьBattleInputController
Лавка, привал, сундук, ивент, награда, золотоShopController, CampScreenView, MenuRouter, RunStateService
Петли (музыка/амбиент)FmodAudioService держит EventInstance; Stop/StopAll

7.4. Тесты

Assets/_Project/Tests/EditMode/Audio/AudioCoverageTests.cs:

  1. каждый ключ, который зовёт код (литералы Play("…") и PlayUi("…")), резолвится в каталоге;
  2. каталог совпадает с FMOD-манифестом в обе стороны (ловит «пересобрал FMOD, забыл каталог»);
  3. в каталоге нет пустых EventReference.

Полный EditMode-прогон после работы: 539/539.

7.5. Материал и лицензии

ПакЛицензияГде
Kenney: impact-sounds, interface-sounds, ui-audio, rpg-audio, music-jinglesCC0Assets/Kenney/* (последние три докачаны 2026-07-26)
RPG Essentials Free (Leohpaz)бесплатно в коммерческих, кредит опционаленAssets/Kenney/RPG_Essentials_Free
Музыка: Fantasy Orchestral Theme, Town Theme (cynicmusic), Battle Theme A (cynicmusic), Loopable Dungeon AmbienceCC0, OpenGameArtFMOD Project/MusicSource

Sonniss GDC не использовался: длинные дизайнерские файлы требуют нарезки на слух, а Kenney CC0 закрыл все P0-дыры.

8. Приёмка: что слушать

  1. Бой 2×2+: удары/смерти/хилы примерно одной громкости, каши нет; пачка добиваний даёт один стингер, а не наложение.
  2. Слоумо на добивании и скорость 2×/3×: боевая шина едет по питчу, UI и музыка — нет.
  3. Статусы: заморозка/стан/яд/щит слышны отдельно от обычного удара.
  4. Разлёт на осколки звучит после смерти, а не вместе с ней.
  5. Интерфейс: клик, наведение, табы, драг-дроп, отказ (недоступный узел, дроп мимо, покупка без золота) — отличимы на слух и не спорят с боем по громкости.
  6. Карта: наведение на узел, выбор, недоступный узел, приезд фишки.
  7. Музыка: меню → карта → бой переключаются без наложения; в исходе забега музыка уходит.
  8. Слайдеры настроек реально меняют громкость (раньше работал только «Общий»).

Что крутить, если что-то не так: числа в scripts/audio/audio_map.py (CATEGORIES, BUS_TREE, TARGET_RMS_DB) → перегон пайплайна; либо живьём через FMOD Live Update, а потом перенести значения в карту, чтобы следующий прогон их не затёр.

8a. Инструменты отбора и проверки сэмплов

Уши в этом контуре есть только у Макса, поэтому всё, что можно проверить числами и моделью, проверяется скриптами.

ИнструментЧто делаетЦена
scripts/audio/audit_samples.pyтехнический брак: клиппинг, DC-offset, тишина в начале (звук опаздывает за событием), обрыв хвоста, длительность вне нормы категории, частотабесплатно, ffmpeg
scripts/audio/clap_pick.py --findподбор кандидатов из всех паков репо по описанию словами (CLAP)бесплатно, локально, venv ~3 ГБ
scripts/audio/clap_pick.py --verifyсверка: похож ли назначенный сэмпл на то, чем его объявили (описания — DESCRIPTIONS в карте)то же
scripts/audio/freesound_fetch.pyпоиск CC0-звуков на Freesound по описанию + превью-кандидатыбесплатно, нужен API-ключ в .env
scripts/audio/sfx_generate.pyгенерация под дыры, которых нет в CC0-паках (ElevenLabs SFX)~$0.002/сек, нужен ключ в .env
scripts/audio/sfx_generate_local.pyто же локально (Stable Audio Open, модель small)бесплатно, нужен HF_TOKEN; на CPU медленно

Про платную генерацию, если понадобится. ElevenLabs Creator — 11), 121 000 кредитов. Звук стоит 200 кредитов при авто-длительности или 40 кредитов за секунду, если длительность задана явно — а наши SFX короткие. Значит месяц покрывает ~3000 односекундных или ~1500 двухсекундных генераций; на весь каталог (109 событий × 5 вариантов) нужно порядка 500. Одного месяца хватает с тройным запасом, а кредиты живут максимум три месяца — поэтому подписку имеет смысл брать не заранее, а под готовый список промптов.

Граница метода: и аудит, и CLAP отвечают на вопрос «то ли это вообще» — соответствие, брак, уровень. На вопрос «звучит ли это хорошо» они не отвечают и отвечать не могут. Финальный отбор остаётся на слух.

Первый прогон аудита нашёл 26 сэмплов с 30–80 мс тишины в начале (удары и клики ощутимо запаздывали) и 5 за пиковым потолком. И то и другое теперь чинится прямо в нормализации: ведущая тишина срезается, хвост закрывается фейдом 8 мс, у пика есть запас 0.5 dB.

9. Осталось (следующий заход)

  • Тонкая настройка на слух — стартовые числа микса выставлены расчётом, не ухом.
  • Sidechain-дакинг стингеров под боевую шину и mixer snapshot под boss-intro (audio-subbuses §Бонус-стык) — в скрипте пока нет.
  • Слои импакта (transient/body/tail) и intensity-слои ударов — backlog-audio-sfx §1.
  • Адаптивная музыка (vertical layering по интенсивности боя) — сейчас одна дорожка за раз.
  • Позиционность: весь звук 2D; пан по позиции юнита на арене — backlog-audio-sfx §5.
  • Немыми осознанно оставлены: шаги, промах снаряда, приземление после отбрасывания, area-hit (в AreaHit нет id источника), смена режима камеры, выход из игры.

Связи