Кто я? Чего я хотим?
Всем привет, меня зовут Виктор. Сейчас я работаю техлидом игровых Unity-проектов, а до переездов руководил отделом разработки в EdTech, Digital Twins, инфографике и картографических сервисах. В EdTech я много лет занимался симуляторами физики (электричества, цифровой логики, механики, гидростатики, МКТ и т. д.). Обожаю реалтайм-3D.
Про проект
Мой проект — Experimenters Circuits. Это кроссплатформенный симулятор стенда для занятий электротехникой, интерактивный практикум по электричеству. В нём есть приборы и компоненты, которые можно соединять кабелями. Симуляция электричества идёт всё время, и схему можно собирать и переделывать прямо во время симуляции. Ошибаться никто не запрещает, но приложение реагирует на ошибки. Например, если подключить амперметр параллельно лампочке и случится короткое замыкание, источник отключится от перегрузки, а некоторые приборы выйдут из строя.
Интерактивный практикум работает в браузере на experimenters.app, есть приложение для iPhone, iPad и Mac. Другие платформы в процессе.
Проект изначально планировался как игра-головоломка на геймджеме, но из-за моей профдеформации уровни оказались очень сложными. В итоге я отбросил идеи геймификации и сделал его образовательной песочницей. По мере разработки от многих фич и идей пришлось отказаться, и за год получился огрызок прототипа.
Проект пока что нельзя назвать удачным, и делаю я его долго. Года полтора я делал его с разным темпом, потом примерно столько же делал вид, что делаю, и прокрастинировал. В конце этого лета (2026) вернулся в разработку. А пока вернемся в 2024.
Оказывается, вам нужны пользователи!

Когда некое подобие прототипа было готово, я собрался с духом и понёс его в стартап-инкубатор в Загребе (Хорватия). Это была предынкубация для начинающих. Проект заинтересовал «жюри», и меня пригласили. Из 10–15 участников только у меня не было питч-дека, зато был прототип. Программа занимала много времени. Нам помогали с проверкой гипотез, но многие там в целом занимались формированием своей идеи. У меня свое видение было, но не было пользователей. Поэтому я искал способы, как их найти, и надеялся, что там мне с этим помогут. Советы сводились к тому, чтобы выходить на репетиторов лично и строить через общение с ними комьюнити.
Директор одной крупной хорватской телеком-компании на афтерпати за пивом сказал, что проект очень интересный, но... «Как ты его продашь? Кому? Образовательный проект не пройдёт через государство, нужны лицензии и т. д. К данным учеников тебя никто не подпустит (GDPR и тд) У учителей своих денег нет, и они сами ничего не закупят».
Демотивацию он закончил предложением прийти к нему, когда у проекта появится тысяча регулярно возвращающихся пользователей в месяц. У меня в голове пронеслось, что с тысячей пользователей инкубатор мне был бы не нужен (уверен, что я был неправ).
Ну что же, я начал собирать анкеты учителей в Хорватии и США. Отзывы и ответы приходили весьма позитивные, но довольно быстро стало понятно, что функционала учителям не хватает. Вот ведь неожиданность, оказалось, что писать код и одновременно делать маркетинговые исследования (а еще менять страны и искать работу) у меня получается плохо. Из последних сил я сделал запуск на Product Hunt, где в своей категории занял второе место недели. Абсолютно бесполезное, но я цеплялся за каждую ачивку-мотиватор, как за соломинку. Потом подал проект в Unity for Humanity, там тоже мимо.
Ну и пожалуйста, ну и не нужно

Нет, я не могу сказать, что в какой-то момент решил бросить проект. Но в мозгах включились блокировки. То баг в солвере, из тех, что весь вечер воспроизводишь, прежде чем к нему подступиться. То пытаешься добавить новый элемент, ради которого надо пройти все шаги от дизайна 3D-модели и раскладки её интерфейса в 2D до логики. То дурацкая иконка, на которую уходит полвечера. Было ощущение, что я за всё плачу фикс прайс. Поправить иконку? Минус вечер. Поправить баг? Минус вечер. А иногда вечер уходил, и я не получал вообще никакого результата.
Дошло до того, что я что-то начинал и не мог закончить неделями. Открываешь Houdini или Unity (3D-модели у меня простые, но процедурные), полчаса крутишь сцену, читаешь свой легаси-код, который писал, чтобы побыстрее сделать прототип, отвлекаешься на чатики и закрываешь. А дальше я начал не начинать. Я года полтора говорил людям, что всё ещё работаю над проектом, хотя на деле это означало только то, что я его не удалил.
Потом смена работы, второй переезд, дела, кранчи. Ну и, конечно же, у меня завёлся ДРУГОЙ пет-проект (и не один).
Начало продолжения

Думаю, вы заметили, что за последние несколько лет в разработке кое-что поменялось. Пришли нейронки, агентные подходы, и жить стало проще, жить стало веселей. Я получил возможность скинуть с себя часть бремени исполнителя (например, рутину в вёрстке интерфейсов) и упростить себе контроль качества и ускорить полировку фич. В какой то летний вечер под пиво я открыл проект. И понеслась.
Я задокументировал всё, что есть в проекте, и поднял все свои TODO, известные баги и ограничения. Я долго рассуждал с нейронками на разные темы, и рассуждения превратились в довольно чёткие ТЗ, плюс я восстановил хронологию проекта и его актуальное состояние в своей голове. Я знал, чего хочу и как это должно быть сделано, я лишь не хотел делать это сам. Часть проблем требовала серьёзного рефакторинга и воняла всякими shotgun surgery. При этом мне нужно было двигаться быстро и не сломать то, что уже работало. Поэтому первым делом я уничтожил брошенные посередине фичи, и сделал несклько шагов назад по уничтожению легаси. Далее сделал в приложении сохранение и загрузку схем, и это открыло мне дополнительный путь к автоматизации тестирования. Я написал много тестов, которые проверяли, что всё работает так, как задумано (нет, оно не работало как задумано), что уравнения не падают, в том числе на большом количестве нелинейных элементов, что выполняются законы Кирхгофа и т.д.
После этого трогать ядро стало не так страшно, и я начал рефакторить решатель уравнений и добавлять новые элементы.
Мне нужны были новые приборы. Учителя просили осциллограф. Давно, но просили. Я поднял на питоне сервер, который общался с Houdini. У меня уже были процедурные 3D-модели, что-то вроде китбаша из кнопок, правил для корпусов и раскладок экранов, и это идеально подошло для автоматизации нейронкой. Я дал ей описание того, что хочу получить, спецификацию осциллографа, раскладку кнопок и пример моделей которые уже были готовы. За один день я получил на выходе 3D-модель, UI и биндинги для интерфейса осциллографа. Тогда я понял, что могу делать новые приборы в разы быстрее, чем раньше, пошел закрывать эту часть бэклога а потом принялся полишить.

За 2 недели отпуска я перешёл в режим кранча, поймал вдохновение и сделал вот что.
- Сохранения, отмену и повтор действий.
- Привязку элементов к сетке.
- Оптимизацию солвера по памяти и сложности алгоритмов.
- Переезд на новый UI.
- Локализацию на несколько языков.
- Исправления багов в инпуте.
- Какую-никакую вёрстку для мобилок.
- Новые элементы в базе.
- Осциллограф. Этот парень достоин отдельного упоминания-пункта.
- API для автоматической записи роликов по сценариям приложения.
- небольшие микро фичи вроде блокировки взаимодействий с проводами и тд.
- Базовую реализацию серверной части
Дальше про некоторые пункты подробнее.
Тесты, которые тыкают за меня

Я решил, что буду выпускать это на все доступные мне платформы. Почему? Да потому что я могу. Под мобилки нужна была резиновая вёрстка, и тут я попытался придушить в себе перфекциониста и представил перед собой две альтернативы. Либо я ковыряюсь с этой вёрсткой сам, либо отдаю её нейронке. Я начитался твитов дяди Боба о том, что не надо читать весь код, достаточно зажать агентов в жесточайшие рамки тестов и приёмки, вроде мутационных тестов, property-based тестов, тестирования с оракулом и сравнения с эталонными результатами. Так унылая часть работы ушла нейронке, и резиновая вёрстка под мобилки появилась.
Мой код инпута приложения был не готов к части новых жестов, которые мне понадобились, например к щипку и одновременному нажатию на разные 3D-кнопки. Для жестов у меня своя система событий поверх Unity Input System, и события в ней ходят примерно как в WPF и MRTK. Как в WPF, жест сначала достаётся ближайшему объекту под указателем, и если тот его не обработал, событие проваливается глубже, к объекту под ним, и в конце концов к камере. Жест драга по проводу снимают его с клеммы, а при включённой блокировке проводов провод его пропускает, и протяжка доходит до прибора под ним. Как в MRTK (mixed reality toolkit от Microsoft), мышь, палец и перо для объектов одинаковые указатели, и их может быть несколько сразу. Я начал рефакторить инпут, понимая, что правка инвазивная и всё это неплохо бы прокликать заново, причём желательно на таче. Руками делать это очень долго, и я хотел, чтобы руки были не мои.
Пришлось автоматизировать и это, так что теперь тыкают тесты Play Mode. Жесты в них делают виртуальные мышь, тачскрин и перо (стилус) из Input System. Приложение не отличает их от настоящих, так что каждое нажатие проходит тот же путь, что и пользовательское. А виртуальный тачскрин даёт в редакторе несколько пальцев, которых у одной мыши нет. Конечно я не уверен что поймал все. Но несколько старых багов о которых я помнил я таким образом поправил и обмазал тестами.
Тест идёт по различным заготовленным сценариям и запрашивает у самого прибора, куда нажимать. Я дал каждому прибору API, которое отдаёт мировую позицию его клеммы или центра кнопки, а тест через камеру переводит её в точку на экране, и разрешение экрана и расстановка приборов в схеме ему безразличны.
Я не скупился на сценарии. В одном из них с источником питания работают сразу три пальца. Первый жмёт и держит кнопку, второй в это время крутит ручку, третий тянет стену рядом, а тест следит, чтобы камера не дёрнулась. Такие сценарии есть для всех приборов. Я даже заморочился и сделал имитацию потери устройства ввода, например Bluetooth-мыши с севшей батарейкой. Тест выдёргивает мышь посреди перетаскивания, и приложение должно отпустить прибор, который эта мышь тащила, чтобы тот не остался висеть на указателе-призраке.
Всего набралось около 60 душных пользовательских сценариев. Они запускаются как обычные тесты и все вместе насилуют приложение минут пять.
Многие эксперименты и подходы теперь быстро превращаются в скрипты и инструменты. Какие-то приживаются, какие-то нет. Неприжившееся и неудобное я предпочитаю удалять, а прижившееся автоматизировать и переиспользовать.
Есть тест, который проверяет, что каждое окно приложения отрендерилось и текст влез (на каждом языке, включая RTL-арабский) на разных разрешениях и ориентациях экрана.
Есть тест, который проверяет, что 3D-кнопка интерактивна и влияет на прибор.
Есть тест, который хватает прибор, тащит его к краю экрана и проверяет, что камера начала скроллиться.
Есть тест, который сверяет поведение моего солвера на разных примерах с солверами конкурентов и с заранее посчитанной математикой на питоне, и т. д. и т. п.
Есть ли бесполезные тесты? Ага. когда нибудь я их разгребу если будут мешать

Глядя на то, как мои тесты сами гоняют приложение, я решил делать обучающие ролики и начал с гайда по использованию приложения. Все гифки в нём обновляются одной кнопкой при деплое новой версии. Для каждой гифки у меня есть сценарий, и приложение проигрывает его через тот же API, что и тесты. Виртуальная мышь наводит, нажимает и тянет, кнопки подсвечиваются и продавливаются, ручки крутятся, а точки для жестов сценарий берёт у самих приборов, у гнезда, корпуса или кнопки. Кадры снимаются по одному на шаг игрового времени, так что ролику всё равно, тормозит ли редактор, а в конце ffmpeg собирает их в зацикленную гифку. Если я поменяю дизайн экрана осциллографа, сценарий сам загрузит схему с фигурой Лиссажу, перезапишет ролик и выложит его в гайд.
Как подключить лампочку к батарейке, или чего на самом деле хотят пользователи

Потом я наконец-то сделал сервер с учётными записями и доделал сайт. Я перевёл сайт на несколько языков, и туда начали приходить посетители. Самое смешное, что делал я песочницу, в которой можно собрать произвольную схему, а топовые запросы, по которым люди приходят, это «как подключить лампочку к батарейке» и «закон Ома». Окей, добавил батарейку. Всё это время им, скорее всего, буквально хватало самых базовых картинок из интернета, вроде такой.
Больше всего мне как разработчику нужны от пользователей их схемы. Но оказалось, что люди предпочитают не регистрироваться и не сохранять схемы. Тогда я решил собирать баги. Я подключил Sentry и начал получать сообщения об ошибках, особенно когда у пользователя падают расчёты. Теперь, если у кого-то случается внештатная ситуация и он разрешил отправлять такие данные, мне на сервер прилетает схема, расчёт которой не удался. Кое-что я уже исправил и надеюсь, что таких ситуаций станет меньше, а справляться с ними я буду быстрее, потому что увижу, что именно не получилось. Ведь у меня нет ни отдела QA, ни комьюнити-менеджеров.

Стек
Приложение написано на Unity и C#, решатель уравнений свой, тоже на C#. Ввод сделан поверх Unity Input System, жесты проверяют тесты Play Mode. 3D-модели процедурные и собираются в Houdini, а нейронка работает с ним через сервер на Python. Эталонную математику я считаю на Python в SciPy и сверяю с ngspice и другими либами. Учётные записи живут на Node с Postgres, ошибки собирает Sentry, аналитику Unity аналитика, а гайд, документация и базовые тексты сделаны пока на MkDocs.
Что дальше
Сейчас моя основная цель — рост пользовательской базы. Для этого нужно что-то сделать с ретеншеном и добиться нормальных регистраций пользователей. Как на проекте зарабатывать, я не знаю.

А пока буду фармить карму, для учеников приложение я планирую оставить бесплатным. Если плата и появится, то, может быть, за слоты сохранений и какие-то сервисные фичи, а может быть, платить будут учителя и частные школы за задания для класса, встраивание лаб в Moodle и Google Classroom и рабочие листы в PDF. Когда это будет, хз. Пока я пишу учителям и репетиторам из разных стран и пытаюсь понять, чего им не хватает.
Чем может помочь Клуб
Если вы учитель или репетитор физики, а может, знаете такого, мне очень интересно, чего не хватает на уроках. А если вы просто покрутите симулятор на experimenters.app и напишете, что непонятно или сломалось, это тоже очень поможет.
