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

Простыми словами
До этого обновления к созданной вами комнате не присоединялся никто. Вы выбирали режим, открывали стол и ждали — чаще всего в одиночестве. Цифры за шесть недель до выхода этого обновления говорят сами за себя:
| Что мы измерили | Результат |
|---|---|
| Комнаты, открытые игроком и так и не начавшиеся | 38 |
| Среднее ожидание, прежде чем человек сдавался | ~3,2 часа |
| Комната, заполненная 4/4 и всё равно не начавшаяся | ждала 2 дня |
| Завершённые партии, где было больше одного человека | 46 из 231 |
Теперь стол заполняется сам. Через двадцать секунд — четыре минуты после того, как вы открыли комнату, кто-нибудь садится. По лобби ходит дюжина завсегдатаев — с обычными никнеймами, а не «РобоБарон», — и каждый из них при новой встрече остаётся тем же игроком:
- постоянный характер: один и тот же соперник всегда играет осторожно или всегда переплачивает на аукционе;
- дата регистрации и уровень опыта, придуманные не сегодня утром;
- собственный темп — они думают от одной до шести секунд, а не ровно две, как метроном;
- иногда короткая фраза в чате вместо стикера;
- никакой пометки «bot» в комнате ожидания, потому что за столом они ведут себя как игроки.
Они также попадают в таблицу лидеров — это честно, раз уж они играют настоящие партии, — но с жёстким дневным потолком заработка, так что поднимаются как обычный игрок, а не фармят рейтинг.
Для тех, кому интересны детали
Двенадцать персонажей создаются идемпотентно при запуске игрового демона и помечаются значением, которое таблица профилей поддерживала и раньше, — поэтому вся функция вышла без единой миграции базы данных и без изменения игровых правил.
Заполнение — это тик планировщика, а не таймер на каждую комнату. Любая задержка выводится из псевдослучайного числа с зерном по комнате и месту и привязана к отметке времени в базе, поэтому после перезапуска демон продолжает то же расписание, а не разыгрывает задержки заново:
// scheduler.ts — seeded per (room, seat), anchored on a DB timestamp
const rng = createRng(`${game.id}:fill:${seat}`);
const isLastSeat = freeSeats === 1;
const delayMs = isLastSeat
? rng.int(LAST_SEAT_MIN_MS, LAST_SEAT_MAX_MS) // 3-8 min: leave room for a human
: rng.int(SEAT_MIN_MS, SEAT_MAX_MS); // 20 s - 4 min
if (Date.now() - Date.parse(game.created_at) < delayMs) continue;| Параметр | Значение |
|---|---|
| Задержка занятия места | 20 с – 4 мин, с зерном по месту |
| Последнее место | 3 – 8 мин |
| Автостарт, когда стол полон и тих | 3 мин |
| Время «раздумий» бота | 1 – 6 с, с поправкой на персонажа |
| Опыт персонажей | не более одной награды в сутки по UTC |
Три дефекта проявились только на живом запуске, и ни один юнит-тест их бы не поймал: полные комнаты исключались из выборки кандидатов на заполнение, поэтому автостарт никогда не видел именно тот случай, ради которого существует; задержка «раздумий» ключевалась по партии, а не по боту, из-за чего пауза на ходу блокировала ставку того же бота на аукционе, пока его не выпихивало автопасом из собственного окна; и партия, запущенная демоном, не будила планировщики из режима простоя, из-за чего между стартом и первым броском бота проходило 11,6 секунды вместо 49 миллисекунд.
