Расовые бонусы к характеристикам исчезали навсегда: как баг черновика в мини-приложении DnD-бота

Расовые бонусы терялись не в бэкенде, а в мини-приложении создания персонажа: черновик визарда в sessionStorage хранил статы без бонусов расы, и при восстановлении на шаге "Обзор" тихо уходила чистая база без надбавок. Модификатор характеристики у соседних значений (14 и 15) одинаков, поэтому хиты не менялись и баг не был виден на глаз.
Расовые бонусы к характеристикам исчезали навсегда: как баг черновика в мини-приложении DnD-бота
Ребят, разбираем баг, который выглядел так, будто игру ведёт нечестный мастер. 8 августа один из игроков написал: у его паладина после первого боя пропали бонусы к характеристикам от расы. Не “глюк на экране” - сила, телосложение и остальные статы правда стали меньше, и это било по хитам и попаданиям. Игрок перепроверил на нескольких персонажах трижды - бонусы пропадали стабильно. Для DnD-бота, который сам ведёт персонажа без мастера-человека за столом, это не мелкая придирка: если статам нельзя доверять, нельзя доверять и бою.
Как расовые бонусы пропадали у персонажа в DnD-боте
Расовые бонусы к характеристикам у персонажа исчезали навсегда после первого боя или примерно десяти ходов - не временный глюк интерфейса, а тихая порча данных в базе. Игрок воспроизвёл это трижды на разных персонажах, и каждый раз итог был один: бонус от расы просто переставал существовать.
Дальше можно было либо гадать по описанию игрока, либо смотреть в саму базу. Пошли вторым путём - и первый же прод-персонаж всё подтвердил.
Жалоба игрока - это всегда пересказ ощущений: “стал слабее”, “промахиваюсь чаще”. Полезный сигнал, но не доказательство и не адрес бага - на слово “слабее” можно чинить не то и не там. Доказательством стала не переписка с игроком, а сравнение конкретных цифр в базе до и после - то есть подтверждение самого факта порчи данных, а не только жалобы.
Как подтвердили баг по прод-данным, а не на словах
Баг подтвердили не пересказом жалобы, а прямым сравнением строк в базе: у сломанного персонажа не хватало ровно бонуса расы, у контрольного - всё было на месте. У паладина 3 уровня в базе оказалось ровно 27 очков point buy без человеческого +1 ко всем характеристикам - как будто расу при создании вообще не выбирали. А у контрольного персонажа, которого никто не трогал после создания, бонусы стояли нетронутыми.
Это сразу отсекло половину версий. Дело было не в расе как таковой и не в самом факте создания персонажа - дело было в чём-то, что происходило уже после, при каком-то действии над уже существующим драфтом или персонажем.
Почему бэкенд был ни при чём
Бэкенд оказался не виноват: характеристики персонажа входят в список защищённых полей, и любое обновление профиля через API их трогать не может физически - они просто не долетают до кода записи. Логичный первый подозреваемый отпал сразу.
Мы уже разбирали похожую логику расследования, когда NPC начал таскаться за героем по всем сценам подряд - там баг тоже нашёлся не там, где искали сначала. Здесь то же самое: раз защита от порчи в обновлении персонажа держит, вектор нужно искать в другом месте - там, где персонаж вообще появляется.
Где терялись бонусы: черновик формы в sessionStorage мини-приложения
Бонусы терялись в черновике мастера создания персонажа: он хранился в sessionStorage браузера без данных о расовых бонусах, и при восстановлении на последнем шаге отправлял в базу чистые характеристики - без единого предупреждения. Цепочка выходила такая: игрок уходил со страницы мастера персонажа, черновик оставался жить в sessionStorage, но помнил только сырые статы, без бонусов расы. При возврате мастер персонажа честно восстанавливал черновик на шаге “Обзор” - вот только бонусы в нём никогда и не сохранялись. Игрок жал “Создать”, и в базу тихо уходила голая база характеристик, будто расу выбрали заново и без плюсов.
Формально всё работало: черновик сохранялся, черновик восстанавливался, персонаж создавался. Ни одной ошибки, ни одного красного лога - просто в середине цепочки терялось ровно то поле, которое давало игроку его расовое преимущество.
Черновик в sessionStorage браузера - обычное решение для мастера создания персонажа: шагов много (раса, класс, характеристики, снаряжение, предыстория), и если хранить всё в памяти вкладки, случайное закрытие или обновление страницы посреди процесса убивает весь прогресс. Проблема тут не в самом sessionStorage, а в том, что именно в него положили при первой версии черновика: сырые значения характеристик, но не бонусы расы, которые эти значения объясняют. Черновик помнил цифры, но забыл, откуда они взялись.
Почему баг не было видно по хитам персонажа
Баг маскировался под особенность механики D&D 5e: соседние значения характеристики часто дают одинаковый модификатор, поэтому пропажа бонуса на глаз не читалась по производным статам вроде хитов. Модификатор в D&D 5e считается по формуле “(характеристика минус 10), делённое на два, с округлением вниз” - а значит 14 и 15 очков телосложения дают один и тот же модификатор +2 (dnd5e SRD, Ability Scores and Modifiers). Максимум хитов паладина с CON 14 и CON 15 совпадал один в один. Игрок и мы вместе с ним смотрели на итоговое число хитов - и оно ничего не подозрительного не показывало, хотя характеристика под ним уже была не той.
Вот почему баг несколько недель мог сидеть в проде незамеченным: там, где механика D&D сама сглаживает разницу в единицу очка, тихая порча данных умеет прятаться идеально.
Как починили расовые бонусы: новая версия черновика и живой QA
Расовые бонусы починили, подняв версию схемы черновика с 4 на 5: теперь бонус расы хранится прямо в черновике, а на отправке персонажа стоит страховка - если бонусов в восстановленных данных вдруг нет, движок пересчитывает их заново перед записью в базу. Фикс собирали не “на глаз”: сначала написали тест, который специально ловил старое поведение - восстановление черновика и отправка персонажа отдавали 15 очков силы вместо ожидаемых 16, тест падал красным, пока фикса не было. Дальше - 253 автотеста зелёные, из них десятки прицельно про этот сценарий.
Но зелёные тесты - это ещё не доказательство. Мы уже писали, что “тесты прошли” и “бага больше нет” - разные утверждения, поэтому фикс дополнительно прогнали живьём на проде: уход со страницы и возврат к “Обзору” у персонажа-человека - бонус на месте, характеристики сходятся с “база плюс единица” по всем шести пунктам. То же самое проверили на полуэльфе с бонусами к двум разным характеристикам сразу - оба бонуса и харизма от расы остались нетронутыми. Отдельно проверили, что фикс не задвоил бонусы там, где раньше всё работало правильно, и что обычный путь создания персонажа без всяких уходов со страницы не сломался. Четыре сценария, четыре “работает”.
Статы персонажа, который уже пострадал от бага, поправили отдельным разовым скриптом - с проверкой, что после записи в базе действительно оказалось то значение, которое туда записали, а не что-то ещё.
Автотесты и живой QA здесь закрывали разные риски. 253 теста в CI проверяют, что код делает то, что написано в коде - они бы поймали, если бы фикс сам себя сломал при следующей правке. А то, воспроизводится ли реальная цепочка “ушёл со страницы → вернулся → создал персонажа” именно так, как её проходит живой игрок в браузере, показывает только прогон на проде. Тест мог быть зелёным и до фикса - просто проверял не ту цепочку событий.
Что делать, если в приложении для DnD пропадают статы персонажа
Если статы персонажа изменились без вашего участия - не списывайте на глюк интерфейса и не начинайте бой заново: сверьте лист персонажа с расой и бонусами по правилам, а если расхождение подтвердится - пишите в поддержку бота, это чинится на уровне данных, а не боем. Если ваш чарджен живёт хоть немного в браузере - черновик, автосохранение, восстановление после случайного закрытия вкладки - держите в уме, что производные статы врут вам чаще, чем кажется. Округление модификатора характеристики в D&D 5e - штука удобная за столом и опасная при отладке: она умеет прятать баг ровно там, куда вы смотрите в первую очередь.
Если вы ещё не пробовали водить или играть через бота - с чего начать, разбирали отдельно в гайде по первому запуску. А если сталкивались с похожей пропажей статов на своём персонаже - пишите, разберём.
Частые вопросы
Почему баг с расовыми бонусами в DnD-боте не находили по хитам персонажа?
Модификатор характеристики в D&D 5e округляется вниз: 14 и 15 очков телосложения дают одинаковый +2. Хиты и другие производные статы совпадали что с бонусом расы, что без него - по ним баг было не увидеть.
Как починили баг с потерей расовых бонусов в черновике персонажа?
Подняли версию схемы черновика с 4 на 5, стали хранить расовые бонусы прямо в драфте и добавили страховку на отправке - пересчёт бонусов, если их не оказалось в восстановленных данных. Проверили TDD-тестом и живым QA на проде.
Кто виноват в потере статов - бэкенд DnD-бота или интерфейс создания персонажа?
Бэкенд был ни при чём: характеристики персонажа защищены от изменения через обновление профиля. Баг жил на шаге создания - в восстановлении черновика мастера персонажа из sessionStorage без данных о бонусах расы.
Как готовился материал: черновик собирает мой ИИ-конвейер по темам из практики таверны, факты сверяются с первоисточниками и правилами, финальную версию я читаю и правлю руками перед публикацией. Обложку тоже рисует нейросеть. Про кухню — на странице о проекте.