В Soul Strip выход в меню отменял проигранный бой. Нашёл игрок

Пиксель-арт Soul Strip: героиня против Костяного голема, за которым повторяются два призрачных силуэта

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

Soul Strip это роглайк-декбилдер. Забег идёт по карте из узлов, где-то бой, где-то событие с выбором, и на каждой развилке игрок сам решает, по какой ветке двигаться дальше. Бой ведётся колодой из карт с девушками, и цикл атак у каждого из 34 врагов фиксированный: следующий ход врага считается, а не угадывается. Занимает забег чуть больше двадцати минут и кончается смертью или последним боссом, а смерть отправляет в начало.

Эти двадцать минут стоят чего-то ровно потому, что их можно потерять. Баг тихо возвращал их обратно.

Переигрывался не только бой

Обещание. Игрок берёт на забег одно условие: например, нанести за один ход столько-то урона выбранной картой. Часть условий засчитывается по лучшему результату за весь забег, а лучший результат забег хранил у себя, не в узле. Значит, один и тот же бой перезапускался, пока нужный ход не соберётся, и всё зачтённое оставалось зачтённым.

Событие. Выпал Светлый родник, вышел, зашёл ещё раз, и это уже Торговец зельями. Какое событие достанется, решалось не на клике по узлу, а в тот момент, когда открывался экран события, поэтому выход и возвращение прокручивали выбор заново.

Забег держится на решениях, которые нельзя отыграть назад, и всё напряжение растёт оттуда. Возврат к развилке делает любой тяжёлый бой необязательным. А игрок, который эту лазейку нашёл, разучиться её видеть уже не может: дальше она есть в каждом забеге, пользуются ею или нет.

Узел засчитывался до боя, а загрузка его отменяла

С самим сохранением всё было в порядке. Файл писался в момент выхода, ничего не терялось и не портилось. Ломалось на стыке двух мест, каждое из которых по отдельности работало правильно.

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

У загрузки было своё правило на случай обрыва. Игра смотрит, на каком экране забег остановился. Не на карте, значит человек ушёл посреди узла, и узел откатывается: пометка снимается, а игрок отступает на тот узел, из которого пришёл, чтобы выбрать заново.

Пиксель-арт: карта забега из узлов, пройденный узел гаснет и рассыпается, а стрелка возвращает игрока к прежней развилке

Запрет наказал бы не того

Первое, что приходит в голову, это запретить: не выпускать из боя или не грузить сохранение, записанное посреди боя. Решение неверное.

Закрытый ноутбук не читерство. Забег идёт двадцать с лишним минут, а за двадцать минут звонит телефон, приезжает поезд, просыпается ребёнок. Такой запрет ловит всех, кто вышел по обычной причине, а таких большинство.

Чинить надо было не выход, а то, что сохранение выбрасывало. Оно знало, в каком узле игрок стоит. Оно не знало, кто ждал его внутри.

Снимок на входе в узел

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

Пиксель-арт: героиня на входе в узел, за аркой ждёт Костяной голем, поверх сцены светится рамка снимка с полосой HP

Хранить один только сид (seed), с которого генератор стартовал, мало. Сид отматывает поток случайных чисел к началу забега, и всё, что забег из потока уже вытянул, вытягивается заново: те же товары в лавке, те же события, те же карты в награду. Снимок хранит не сид, а позицию в потоке.

Событие ради этого пришлось выбирать раньше. До правки игра решала, что достанется, в момент открытия экрана события, и записывать в снимок было нечего. Теперь событие выпадает на клике по узлу.

Эти пометки и читает «Продолжить забег». Есть они, и кнопка ведёт не на карту, а внутрь того же боя с тем же врагом или в то же событие с теми же вариантами. Выход между узлами по-прежнему ничего не стоит: как только игрок уходит на безопасный экран, снимок стирается, и кнопка ведёт на карту, как раньше. Второй выход, уже из восстановленного боя, попадает туда же, куда первый: снимок делается заново.

Тот же голем, тот же родник

Выход посреди боя, «Продолжить забег», и на экране тот же Костяной голем с тем же HP, с которым игрок в этот бой вошёл. Выход посреди события, и это тот же Светлый родник с теми же тремя вариантами: напиться, окунуться за 8 HP ради 5 к максимуму или пройти мимо. Старая сборка в обоих случаях выдавала другое.

Откат писался под того, кто вышел и вернулся, и проверялся так же. Что будет, если выйти нарочно, никто не проверял. Такие дыры находит не тот, кто пишет игру, а тот, кто в неё играет.

Двадцать минут забега снова нельзя вернуть. Soul Strip сейчас на версии 0.9.93, релиз близко. Добавить в желаемое можно на странице в Steam.

Soul Strip: Waifu Card Battler

Карточный рогалик, где каждая карта — вайфу со своей стихией и характером. Сражайтесь в тёмном фэнтези, собирайте комбо, коллекционируйте и прокачивайте любимиц.

В вишлист Steam