javascript

Сонар-управление + Asteroids. Игра на 50 КБ, которой нужен флагманский смартфон

  • суббота, 26 сентября 2026 г. в 00:00:08
https://habr.com/ru/articles/1086688/
Телефон на столе, ладонь сбоку, корабль на экране следует за ней

Телефон лежит на столе. Сбоку от него в воздухе ладонь. Поднимаешь её — корабль на экране поднимается, опускаешь — опускается. Экрана не касаешься. Камера не нужна: телефон пищит ультразвуком и слушает эхо своим микрофоном.

Играть: sonaroids.app (лучше на iPhone). Код: github.com/neokrasav4ik/sonaroids, MIT.

С чего всё началось

Мне попался проект sonar.cool: Мак листает страницы жестами руки, а «видит» их через собственные динамики и микрофон. Мака у меня нет, зато есть iPhone. Отсюда вопрос: получится ли то же самое в браузере на телефоне? Причём не отдельный жест «листай», а непрерывное управление: высота ладони = высота корабля.

Сразу признаюсь: это вайбкодинг

Сам я не написал ни строчки. Обработку сигнала, игру, сервер рекордов, тесты и даже гифку написал Claude Opus 5.5. От меня были идея, решения «так / не так» и проверка каждой версии своей ладонью. Для разбора я записывал с телефона сырые WAV: ладонь по линейке на 5, 10, 15 см. Модель гоняла по этим записям свои варианты обработки, пока цифры не сходились с линейкой. Ещё она сама собрала себе стенды: синтетический микрофон, повтор записанных партий, игру в Chromium без окна.

Как это работает

  • Зонд. Периодический сигнал из 23 тонов от 18,4 до 20,4 кГц, период 512 отсчётов при 48 кГц. AudioWorklet отдаёт поток микрофона без пропусков, так что картина эха считается каждые 10,7 мс.

  • Движение ладони видно по сдвигу фазы эха. Это точно, до долей миллиметра, но со временем уплывает.

  • Положение видно по тому, как эхо отличается от пустой комнаты. Это шумно, зато не уплывает. Обе оценки сливаются: фаза ведёт корабль, положение не даёт ему уползти.

  • Калибровки как таковой нет. Убираешь руку на секунду (игра запоминает пустую комнату), машешь ладонью на 5–15 см над столом, и экран подгоняется под твой размах.

Что не сработало: пик дальности (прыгал на 6–22 см), амплитуда эха (сплошной шум от пятен интерференции), стандартный AnalyserNode (окна слишком грубые) и две руки (Safari даёт только один микрофон).

Почему «нужен флагман»

Ирония в том, что игра весит около 50 КБ в сжатом виде: графика, звуки и пиксельный шрифт нарисованы кодом. А запускается нормально только на хорошем железе. Всё решает полоса 18–20 кГц: динамик и микрофон телефона вовсе не обязаны её пропускать.

телефон

спад к 20,4 кГц

эхо ладони над пустой комнатой

iPhone 16

около −8 дБ

около 40 дБ

OnePlus 13

−33 дБ

10–15 дБ

Redmi Note 10

около −20 дБ

10–15 дБ

Точнее даже так: нужен не просто флагман, а iPhone. OnePlus 13 тоже флагман, но играть на нём почти невозможно. Ещё у него проваливается вход: телефон теряет куски сигнала. Redmi слышит ладонь только со стороны фронтальной камеры, а не со стороны разъёма, где по логике стоят динамик и микрофон.

Часть проблем удалось обойти. Полоса выравнивается по уровню каждого тона: на записи с OnePlus ошибка против линейки упала с 81 до 13 мм. После провала входа прямой сигнал ищется заново. Игра сама выясняет, у какого конца телефона держать руку. Но 25 дБ разницы в эхе не вытянешь, поэтому Android пока «как повезёт с телефоном».

Рекорды без читеров

На сервер уходит не счёт, а вся партия: номер раскладки и высота ладони на каждом шаге. Это несколько килобайт на пару минут игры. Полёт детерминирован: постоянный шаг 60 Гц, генератор случайных чисел от номера раскладки, никаких sin и pow, которые могут по-разному округляться в разных движках. Поэтому сервер переигрывает партию тем же самым файлом правил, что и браузер, и записывает только счёт, который повторился. «Прислать 999999» не получится. Бот, который честно водит ладонью, пройдёт, но пусть сначала его кто-нибудь напишет.

Сервер — Node 22 без единого npm-пакета: node:http и встроенный node:sqlite. Ник — только латиница, цифры и _: ради защиты от желающих вписать в ник SQL-инъекцию.

Что дальше

Игра работает, рекорды пишутся: сегодня, неделя, всё время. Если у вас Android (особенно Pixel или Samsung), попробуйте и напишите в комментариях, как вышло: модель телефона, слышит ли он ладонь и с какого конца. Если игра не слышит ладонь, тап по номеру версии на экране проигрыша включает журнал, его можно приложить к issue на GitHub.

Ссылки: