Мультиплеер для интровертов: создаём голосового попутчика для Euro Truck Simulator 2

Прокомментировать Просмотры: 0

В Euro Truck Simulator 2 обычно тихо: слышны двигатель, щелчки поворотника и подсказки навигатора. Можно настроить радио или отправиться в конвой с друзьями. А можно сделать так, чтобы на пятнадцатом канале всегда кто-то был на связи.

«Канал 15» — бесплатное приложение-компаньон для ETS2, которое имитирует радиоканал дальнобойщиков. В эфир выходят восемнадцать вымышленных водителей: у каждого свои голос, характер, смены, маршруты и грузы. Игрок нажимает кнопку на руле, произносит фразу вслух — и получает ответ.

Это не мод: приложение не изменяет игру. Оно лишь устанавливает стандартный плагин телеметрии в папку плагинов и считывает данные из общей памяти игры. Проект активно разрабатывается около двух недель, текущая версия — 1.34. Ниже расскажу, как всё работает и с какими трудностями мне пришлось столкнуться.

Цепочка обработки: тангента → текст → намерение → ответ

кнопка на руле ─► микрофон (буфер 0,4 с до нажатия)
               ─► faster-whisper small, CPU, int8
               ─► классификатор намерений
               ─► колода реплик ─► радиоэффект с учётом расстояния ─► динамики

Вся обработка происходит локально. Голос игрока никуда не передаётся — это было принципиальным условием с самого начала.

Распознавание речи. Для него используется faster-whisper small на CPU: int8, четыре потока. Микрофон постоянно открыт, но запись ведётся только при зажатой тангенте, а также сохраняется буфер за 0,4 секунды до нажатия: люди нередко начинают говорить раньше, чем нажимают кнопку. Аудио обрабатывается блоками по 50 мс: получается двадцать колбэков в секунду вместо ста, а нагрузка на ядро снижается с 2,2% до 0,8%. Разница небольшая, но рядом работает игра, которой нужны все доступные ресурсы.

Модель занимает около 2,2 ГБ памяти. Постоянно держать её загруженной вместе с ETS2 накладно, поэтому она запускается при нажатии тангенты примерно за 1,7 секунды — пока игрок успевает произнести фразу — и выгружается после четырёх минут тишины.

Подсказка для whisper. Без неё фраза «кто в эфире?» регулярно превращалась в «кто вы, Хире?» или «кто вы, Сири?». Помог initial_prompt со словарём канала: «Рация, канал дальнобойщиков. Кто в эфире? Повтори. Приём.» К нему добавлены позывные всех водителей. Всего одна строка позволила избавиться от половины ошибок распознавания.

Другая половина — галлюцинации модели. На коротких и тихих записях whisper уверенно выдаёт «Продолжение следует…», «Спасибо за просмотр» или «Редактор субтитров». Модель обучалась на субтитрах YouTube и добросовестно дописывает типичные финальные фразы. С этим помогает справиться стоп-лист.

Неактивный микрофон или тихая речь. Одна из частых жалоб: «рация меня не слышит». Обычно причина одна из двух: выбран не тот микрофон, например виртуальный «Стерео микшер», либо человек говорит слишком тихо. Различить ситуации помогает разброс между пиковым и средним уровнями сигнала. Работающий микрофон улавливает фон даже в тихой комнате, и разброс составляет десятки децибел. У неактивного устройства сигнал почти ровный, с разбросом всего в пару децибел. В первом случае рация просит говорить ближе, во втором — выбрать другой микрофон.

Намерения

Затем за дело берётся классификатор намерений. Он распознаёт приветствия, вопросы «кто в эфире?» и обращения к конкретному человеку, попытки позвать водителя, которого нет поблизости, просьбы и молчаливое прослушивание эфира. Это не нейросеть: в основе лежат основы слов и набор правил. Главная задача здесь — не столько добиться идеальной точности, сколько определить, что делать, если система не поняла сказанное.

Колода вместо генерации

Все реплики записаны заранее, синтеза речи на лету нет. Из-за этого в общей версии водители не обращаются к игроку по имени: нужной записи попросту не существует.

Эфир формируется из «колоды» с защитой от повторов. У каждой реплики есть вес, время последнего воспроизведения и счётчик. Байки складываются в цепочки: водитель может начать рассказ сегодня, а закончить через несколько рейсов. Сейчас в общей сборке больше трёх тысяч звуковых файлов.

Игрок слышит только тех, кто находится поблизости. У каждого водителя своя смена и собственный маршрут по 382 городам. Чем дальше собеседник, тем хуже связь: радиоэффект зависит от расстояния, и у границы зоны слышимости голос почти тонет в помехах.

Граф знакомств и дружба

Отношения между водителями представлены графом: он хранит сведения о том, кто с кем знаком и кто находится в ссоре. Для отношений с игроком предусмотрена шкала дружбы — от сдержанного «ну, здорово» до приглашения на свадьбу брата. Статус «знакомого» открывается при 35 очках.

Плохие дни и конфликты добавлены намеренно. Если все всегда приветливы, атмосфера становится приторной; тепло особенно заметно на фоне неудачных дней.

Пришлось также устранить возможность накрутки: раньше дружба росла при перезапуске программы и после пустых ответов. Теперь пустой ответ приносит ноль очков.

Подводные камни

0xc0000374. Рация аварийно завершала работу без предупреждения: ни записей в логе, ни сообщений в журнале ошибок — процесс просто исчезал. Код 0xc0000374 указывал на повреждение кучи и сбой внутри winmm.dll, но стек ничего не прояснял, а ошибка проявлялась не у всех. Закономерность удалось обнаружить по датам дампов: сбой совпадал с отключением USB-руля во время опроса кнопок через joyGetPosEx. Исключение возникало в нативном коде, поэтому Python-конструкция try/except его не перехватывала. Решением стал опрос кнопок через SDL (pygame): там отключение джойстика приходит как событие, а не приводит к падению процесса.

«Нет данных». В первой версии на вопрос «кто в эфире?» робот отвечал: «Нет данных». Формально всё верно — поблизости никого не было. Но пользователи воспринимали ответ как признак неисправности. От робота отказались: теперь в пустом эфире отвечает водитель с дальнего края зоны, причём делает это в соответствии со своим характером.

«Стасиан». Whisper распознаёт имя «Стас» как «Стасиан». Кроме того, когда игрок звал водителя, уже покинувшего зону связи, вместо естественного «далеко уехал, не достучишься» раздавалось общее «не знаю». Теперь для таких случаев у каждого водителя есть собственная уклончивая реплика.

Озвученные ремарки. В тексты реплик попадали указания вроде «смеётся», и некоторые из них голос прочитал вслух. Чтобы найти такие случаи, весь эфир — 639 строк — повторно пропустили через whisper и поискали ремарки в распознанном тексте. Нашлось 46 строк; их переозвучили.

Телеметрия: «blocked, код 2». Иногда Windows не разрешает стороннему процессу доступ к отображённому в память файлу игры. Для игрока это выглядит так, будто рация просто молчит. Помогает перезапуск игры. Почему возникает проблема, я пока не знаю — буду рад вашим версиям в комментариях.

Зачем всё это

Мне хотелось создать место, где тебя помнят и тебе рады, но ничего не требуют взамен. Никаких счётчиков «дней подряд»: вернулся через месяц — тебе по-прежнему рады. Вокруг люди, в эфире идут разговоры, а отвечать вовсе не обязательно. Получилось нечто вроде мультиплеера для интровертов.

И напоследок вопросы к инженерам, ради которых я и написал этот текст: как бы вы отличали обращение к водителю от разговора игрока с кем-то в комнате? И как вы боретесь с галлюцинациями whisper на коротких фрагментах, помимо использования стоп-листа?

 

Источник

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов