diff --git a/docs/plan.md b/docs/plan.md index 053c4f9..1dea11b 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -4,7 +4,7 @@ задачи и проработка крупных направлений. Срез-статус архитектуры — `docs/demo.md` (дорожная карта там — историческая, актуальный план здесь). -Дата ревизии: 2026-09-06. +Дата ревизии: 2026-09-07. **Статус проекта (2026-09-06): техно-демка движка.** Основные силы — движок (`packages/engine`); игра — витрина и полигон проверки API. Приоритет решений: @@ -23,6 +23,7 @@ | 4 | Акт 2: новые локации/сюжет по `docs/world.md` | очередь после акта 1+AI-ассетов | | 5 | Замена сгенерированных плейсхолдеров арта на AI/ручной арт | в составе работы B | | 6 | Крупные объекты: footprint в движке, слоты арта (работа C, ниже) | в работе | +| 7 | Возможности аудио-движка (работа D, ниже): sfx-хендл, полифония, наблюдаемость, буферы | 📋 спланировано (D0–D2) | Отложено: **IsoLayout как параметр игры** — `DEFAULT_ISO` в `math/iso.ts` остаётся единственной точкой входа; прокидывание конфига игры через ~15 мест движка не даёт @@ -260,6 +261,64 @@ --- +## Работа D — возможности аудио-движка + +Итог аудио-работ (2026-09, коммиты 57bce47…4c1c56c): в движке — 4 шины, +кроссфейд слотов music/ambience, `playLoop` без слота, pan/rate, шпион +`onPlayed`; в игре — позиционный `AudioSystem`, локальные амбиент-слои, +событийные звуки боя, DEV-лог `__gameAudioLog`. Ниже — потребности, которые +упёрлись в границы `AudioManager`, по убыванию полезности. Игровая логика +(кто слушатель, затухание по дистанции, `playAt`) остаётся в игре — в движок +её не тащить. + +### Потребности + +1. **Управление играющим разовым звуком.** `play()` возвращает `void`: + нельзя остановить, перезапустить «с нуля» (опция «убить прежний экземпляр + ключа») или изменить volume/pan/rate в полёте. Самый чувствительный кейс — + движущийся источник: панорама фиксируется в момент запуска. +2. **Лимит полифонии в движке.** Число одновременных источников ничем не + ограничено — игру спасает только `MIN_INTERVAL` на ключ в игровом + `AudioSystem`. Нужен `maxVoices` с воровством самых тихих/старых голосов — + защита от каскада (десяток `combat:*` в один тик). +3. **Шпион на все запуски + окончание.** `onPlayed` зовётся только для sfx — + лупы (музыка/амбиент/слои) шпион не видит, игре пришлось логировать + амбиент вручную в `Game.playAmbience`. Нет `onEnded` — нечем ловить «звук + доиграл» для синхронизации с анимацией. +4. **Длительность буфера наружу.** `duration(key)` — сейчас её не достать, + а нужна катсценам и генератору шагов (не играть следующий шаг раньше конца + предыдущего). +5. **Играть буфер из памяти.** `playBuffer(AudioBuffer)` — синтез сейчас + только оффлайн (`tools/wav.mjs`); генерировать вариации звука в рантайме + движок не может (сейчас джиттер имитируется `rate`). + +### Этапы + +- **D0 (движок): sfx-хендл + полифония.** `SfxHandle { stop(fadeSeconds?), + setVolume(v), setPan(v), setRate(v) }` — по образцу `MusicHandle`; опция + `restart`/«один экземпляр на ключ»; `maxVoices` с воровством голосов. + Тесты — расширение `AudioManager.test.ts` (фейковый ctx уже умеет). +- **D1 (движок): наблюдаемость.** `onPlayed` на все запуски (sfx + лупы, + опции нормализованы); `onEnded(key)`; `duration(key)`. Игра: убрать ручное + логирование амбиента из `Game.playAmbience` (лог целиком из шпиона). +- **D2 (движок + игра): буферы из памяти.** `playBuffer(buf, opts)` в шину + sfx/луп; игра — вариативные шаги: генерация 3–4 вариаций шага в рантайме + из примитивов `wav.mjs` (сиды фикс.) вместо джиттера `rate`. +- **Проверки каждого этапа**: `npm run check:fast`; агентная проба + `checks/audio.mjs` не деградирует (лог шпиона остаётся источником). + +### Дальше (после D — закрываем «по инструментарию») + +- **Инфраструктура**: mute/suspend при `visibilitychange` (свернул таб — + тишина); `bus` в опциях разового `play`; exponential-фейд (линейный на + громких затуханиях слышен как «ступенька»). +- **Музыкальный слой**: плейлист/секвенция тем («тема лугов → тема боя → + назад»), стем-миксинг — параллельные лупы-слои одной темы с независимой + громкостью (спокойно/настороженно/бой; ложится на существующий `playLoop`). + Контент закрывается инструментарием Работы A (этапы A2–A3), API — здесь. + +--- + ## Открытые решения (принять перед стартом работ) 1. ~~B0: какая из GPT-пиксель-моделей заходит лучше~~ — **закрыто B0+B1**: