В середине нулевых в рунете была игра Ксин, браузерный MUD без единой картинки, только текст, HTML‑формы и таблицы характеристик. Через несколько лет проект умер: сервер выключили, исходники потерялись, остался только форум с обсуждениями механик и кэш в Wayback Machine. Мы собираем игру заново, не «игру по мотивам», а максимально точную реконструкцию: те же формулы, те же монстры, те же предметы.
Ниже не питч и не анонс, а рассказ о том, как это устроено технически: откуда брать данные, когда архиву верить нельзя, и что случилось один раз, когда мы ему поверили не глядя.

Откуда вообще берутся данные
Три источника, и они друг другу противоречат чаще, чем хотелось бы.
Wayback Machine. У старого Ксина не было API и не было дампа БД — только HTML‑страницы, которые кто‑то когда‑то заходил и смотрел, а archive.org их заскринил. Через CDX API Wayback можно вытащить список всех заскриненных URL за всё время жизни сайта, и дальше руками идти по страницам — собирать то, что видно в интерфейсе. Проблема в том, что видно не всё: только из форумных обсуждений, где игроки сами считали урон по логам боя. Страницы мира недоступны.
Форумный архив. Старый форум тоже не пережил закрытие проекта, но часть веток осталась в кэше и в сторонних зеркалах‑архивах фанатских сообществ. Там — не документация, а живые обсуждения игроков: кто‑то жаловался, что крит выносит четверть хп с одного удара, кто‑то спорил о формуле защиты. Из таких постов реально можно вытащить рабочие числа, но на каждое число нужно два независимых подтверждения, иначе это домысел, а не факт.
CSS и вёрстка. Звучит странно, но один из самых надёжных источников — это сохранившийся файл xin.css с оригинальной стилизацией страниц. Оттуда видно вещи, которые нигде не задокументированы напрямую.
Боёвка: собирали из кусочков

Формулы боя — самое дорогое, что можно восстановить, потому что они нигде не были написаны целиком, только проявлялись косвенно.
Часть вещей подтвердилась чисто: HP персонажа = Выносливость × 5, смерть в бою снимает 2,5% опыта, раунды боя обеих сторон считаются одновременно, а не по очереди (это принципиально — очередность полностью меняет баланс). Это подтверждено и форумными постами, и логикой самих архивных страниц боя.
Часть осталась спорной и зафиксирована как именно спорная, а не как факт, например, точный делитель «силы заряда» у магических жезлов. Мы нашли упоминание шкалы 1/2/4/8 на одной архивной странице предмета, но ни разу — вообще ни разу за почти тысячу постов форума — это не обсуждалось игроками.
Бестиарий: два архива, которые друг другу противоречат
Для монстров у нас два независимых источника: подробный текстовый бестиарий, собранный вручную по архивным страницам, и отдельная таблица характеристик по зонам, которую кто‑то когда‑то вёл параллельно. Беда в том, что они не совпадают. Один и тот же монстр в одном источнике живёт у Пасеки, в другом — в Крепости, и опыт за убийство указан разный.
Когда источники расходятся, самое плохое решение — выбрать один «на глаз» и забыть про расхождение. Вместо этого на каждого спорного монстра у нас в заметках явно написано, что именно расходится и какой источник в итоге выбран — чтобы через полгода, когда кто‑то снова наткнётся на ту же цифру в другом месте, можно было сразу понять: это уже разбирали, вот почему выбрали так.
Честно откалибровать удалось пока не всё, пока выверено детально около 40 монстров в восьми зонах: кубики урона, шанс крита и фатального удара сведены по архивным данным построчно, а не унаследованы от прежних случайных значений в базе. Остальной бестиарий всё ещё содержит старые плейсхолдерные числа, это явно помечено в документации проекта, чтобы при следующем проходе не перепутать выверенное с невыверенным.
Как мы один раз сломали прод, поверив своим же расчётам
Отдельная история, которую стыдно не рассказать, потому что именно она многому научила.
Мы переработали формулу урона от оружия и магии и одновременно пересчитали баланс монстров в одиннадцати игровых зонах, всё по новым, вроде бы более архивно‑точным формулам. Код прошёл тесты, пересчёт выглядел логично «на бумаге». Выкатили на прод.
В течение следующего часа стало понятно, что живым игрокам в бою стало не сходиться — где‑то монстры стали непропорционально выносить персонажей, где‑то наоборот. Дело было не в одной ошибке, а в том, что новая формула урона и новая калибровка монстров были рассчитаны на бумаге относительно друг друга, но не прогнаны вместе через реальный боевой движок до выката, а между формулой и конкретными числами монстров оказался зазор, который на бумаге не был виден.
Решение было единственно правильным и неприятным: полный откат к состоянию на момент до выката, в тот же день. Реформа осталась только в dev‑ветке, где её можно спокойно доводить и тестировать, не трогая живых игроков.
Вывод, который с тех пор стал правилом: любое изменение формулы боя сначала прогоняется через симуляцию реальных боёв (у нас это несколько тысяч смоделированных сражений на копии продовой базы) — и только после того, как цифры сходятся на тестовом прогоне, можно думать про выкат.
Что по технике
Бэкенд — Python 3.11, FastAPI, SQLAlchemy в синхронном режиме, PostgreSQL. Фронтенд серверный рендеринг на Jinja2, без единого JS‑фреймворка, что звучит анахронично, но для игры в стиле MUD работает честно: это и есть стилистически верное продолжение оригинала, который тоже был просто HTML‑формами.
Отдельно — своя система патчей поверх Alembic: каждое изменение схемы или данных монстров оформлено отдельным пронумерованным файлом с apply(db), который применяется один раз и трекается в таблице в базе. Это оказалось удобнее для проекта с таким количеством точечных балансных правок, чем держать всё в одних больших Alembic‑ревизиях — проще смотреть историю конкретно боевых изменений отдельно от структурных.
Где это сейчас
Игра работает, в неё можно зайти прямо сейчас на xin.org.ru. Часть контента ещё не выверена по архиву и помечена как временная, часть зон построена, но физически не подключена к карте — ждёт своей очереди. Реконструкция продолжается постепенно, зона за зоной, с тем же правилом: если не подтверждено двумя независимыми источниками, в код не идёт, остаётся гипотезой в заметках.
Источник: habr.com