SLOT ENGINE

Спецификация · алгоритмы · администрирование · руководство по развитию

Next.js 15 + TS strict Pixi 7 legacy (canvas-fallback) PostgreSQL 16 + Prisma RTP цель 95% эталон 66 unit · 25 e2e
⚠️ Только демо/социальный режим. Все балансы — виртуальные кредиты. Депозитов, выводов, платежей и крипты в коде нет и не будет (юридическая граница проекта, см. SECURITY.md). 18+

01Быстрый старт и ссылки

🌐 Страницы

URLЧто это
/Лобби: карточки тем, 🏆 лидеры дня, 💎 джекпот
/demoИгра, тема по умолчанию — Wild Patrik (классика)
/demo?theme=centavraCentavra — sci-fi тема с каскадами
ℹ️ Centavra здесь — вторая тема этого же движка (каталог themes/centavra/: свой арт + своя математика v2.0.0), а не отдельный проект. Не путать с одноимённым проектом centavra-mission-spin (bottle/, порты :3005/:5002) — тот к slot-engine не относится.
/adminАдминка (пароль, см. раздел 02)
/spec.htmlЭтот документ
/production.htmlПродакшн & промо: стандарты рекламных ассетов, ТЗ для генерации, дорожная карта
/investors.htmlДля инвесторов: двухмодельный конвейер (Opus 4.8 × Fable 5), кто что построил, остаток Roadmap
/strategy.htmlСтратегия выхода: рынок, монетизация (USDT vs лицензия), блокчейн/provably-fair, география, поэтапная де-рисковка
/investor-deck.htmlИнвестор-дек: 3 экрана 16:9 (PDF: /slot-engine-investor-deck.pdf)

🖥 Окружения

Прод (смотрит оператор)
http://127.0.0.1:3006 — pm2 slot-engine, каталог .next
Dev / e2e
http://127.0.0.1:3007 — npm run dev, каталог .next-dev
БД
PostgreSQL 16, база slot_engine (пароль в .env)
Секреты
.env — вне git; не читать, не печатать

Никогда не занимать :3006 dev-сервером и не удалять .next при живом pm2. Рестарт прода — pm2 restart slot-engine только после npm run build.

⌨️ Команды

cd slot-engine
npm run dev          # dev-сервер :3007
npm run ci           # lint + 66 unit + прод-сборка (зелёное = можно коммитить)
npm run e2e          # 25 Playwright-тестов (сам поднимает dev-сервер)
npm run build && pm2 restart slot-engine     # деплой на :3006

npm run simulate:rtp -- --config wild-patrik --spins 1000000 --seed 777          # RTP-отчёт
npm run simulate:rtp -- --config centavra --spins 200000 --seed 777 --force-bonus # цена бонуса
npm run validate:assets -- --theme wild-patrik                                    # контракт ассетов

02Админка

Вход: открыть /admin → редирект на /admin/login → ввести пароль. Пароль хранится в переменной ADMIN_PASSWORD файла .env на сервере (в этом документе и в git его нет — это секрет). После входа ставится подписанная HMAC-кука; middleware закрывает все /admin/* и /api/math/*; если пароль в .env не задан — админка отвечает 503.

РазделЧто умеет
/adminДашборд: статистика по дням (спины, ставки, выигрыши, фактический RTP), топ-выигрыши
/admin/roundsВсе раунды с пагинацией: ставка, выигрыш, скаттеры, FS, кап
/admin/rounds/[id]Разбор споров: полный исход раунда с картинками сетки — базовое окно, каждая линия, цепочка фриспинов, каскады
/admin/simulationsИстория RTP-симуляций + кнопка «Запустить симуляцию» (тема + число спинов, живой результат)
/admin/configsВерсии математики тем (что записано в БД при спинах)
/admin/validationОтчёты валидатора ассетов
/admin/errorsЛог ошибок из audit_logs (type=error)

⚠️ Известная гоча: параллельные IDE-агенты дважды ломали src/middleware.ts, вставляя node:crypto — middleware работает в edge-runtime, можно только crypto.subtle. Если build падает с «Unhandled scheme node:» — проверять этот файл первым.

03API

ЭндпоинтНазначение
POST/api/sessionНовая анонимная сессия, стартовый баланс 1000 демо-кредитов
GET/api/session?sessionId=Состояние: баланс, купленные анлоки
POST/api/spinСпин: {sessionId, bet, themeId, buyBonus?} → окно, линии, фриспины, каскады, джекпот, баланс. Единственный источник исходов.
POST/api/gambleРиск-игра ×2: {sessionId, roundId, action: red|black|collect}
POST/api/unlockПокупка фич за демо-кредиты (например premium_sound = 300)
GET/api/history?sessionId=Последние раунды игрока (модалка 🕘)
GET/api/leaderboard?sessionId=🏆 Топ-10 дня + своё место (sessionId опционален)
GET/api/jackpot💎 Текущий промо-пул для тикера
GET/api/themesСписок тем: ставки, ставка по умолчанию
ADMIN/api/math/simulateЗапуск симуляции из админки (закрыт middleware)

Все мутации — за rate-limit'ом (token-bucket: 3 спина/с на сессию, 10/с на IP). Лишние поля в теле запроса отбрасываются zod-схемами — «передать желаемый исход» с клиента невозможно (есть e2e-тест ровно на это).

04Алгоритмы выигрыша

🎲 Генератор случайности

  • Продакшн: crypto.randomInt (CSPRNG ОС, без modulo-bias). Никакого Math.random в исходах.
  • Тесты/симуляции: детерминированный mulberry32 по сиду — его создание в production-сборке запрещено кодом (бросает ошибку).
  • Принцип server-authoritative: исход полностью считается на сервере в одной транзакции (списание → розыгрыш → зачисление → журнал). Клиент только рисует. Математика лежит в src/server/math с пакетом server-only — фронт физически не может её импортировать.
  • Аудит: каждый раунд пишет Round + полный RoundResult.detail (окно, линии, фриспины, каскады) — любой спор разбирается в админке по картинке.

🎰 Базовая механика (обе темы)

Поле
5 барабанов × 3 ряда; каждый барабан — закольцованная лента символов (reel strip), остановка = независимый случайный индекс ленты
Линии
10 фиксированных, выплаты строго слева направо; ставка на линию = ставка / 10
Wild (W)
замещает всё, кроме скаттера; линия читается двумя способами (серия чистых W и серия первого не-W символа с замещением) — платится больший вариант
Скаттер (S)
платит от общей ставки: 3/4/5 шт → ×3/×15/×75; и даёт 12/20/30 фриспинов
Фриспины
все выигрыши ×2, ретриггеры работают, потолок 100 FS на раунд; могут крутиться на отдельных лентах (reelsFs.json, опционально)
Кап
максимум выигрыша раунда — 5000× общей ставки
Частоты
выигрыш ~каждый 3.7-й спин (hit 27.3%), фриспины ~1 раз на 119 спинов
Выигрыш линии = пэйтейбл[символ][длина серии] × (ставка/10) × (фриспины ? 2 : 1)

RTP регулируется составом лент (сколько раз каждый символ встречается на каждой ленте — таблица COUNTS в scripts/calibrate-math.ts) и значениями пэйтейбла. Вероятности честно вытекают из лент — никаких «подкруток» исхода после генерации.

⛓ Каскады — тема Centavra (math v2.0.0)

  • После любого линейного выигрыша выигравшие символы исчезают, выжившие съезжают вниз, новые приходят с той же ленты выше точки остановки — рефилл детерминирован из стопов, отдельного RNG нет, вся цепочка воспроизводима и хранится в журнале.
  • Лестница множителей по шагам цепочки: ×1 → ×2 → ×3 → ×5 (дальше ×5).
  • Скаттеры и фриспины считаются только по первому окну; во фриспинах каскады тоже работают, их ×2 умножается поверх лестницы.
  • Предохранитель: максимум 50 шагов цепочки.
Выигрыш шага = Σ линий шага × множитель лестницы × (фриспины ? 2 : 1)

Включение каскадов подняло «сырой» RTP до 133.78% — пэйтейбл Centavra перекалиброван (×0.71) обратно к цели: 94.85% @1M. Wild Patrik не тронут: контрольный прогон ровно 95.20%.

05Игровые фичи

🃏 Риск-игра ×2

После любого выигрыша: угадай цвет карты — выигрыш удваивается, не угадал — сгорает. До 5 шагов (потом автозабор). Честные 50/50 на crypto-RNG, EV-нейтральна — RTP игры не меняет, только дисперсию. Исход решает сервер (POST /api/gamble); карта (масть/ранг) — производная исхода для показа. Новый спин = молчаливый collect. Защита от гонок — оптимистичный guard по номеру шага.

💰 Покупка бонуса (Buy Bonus)

Мгновенный вход во фриспины за 27× ставки. Реализация — rejection-sampling: сервер перебрасывает стопы до 3+ скаттеров, что сохраняет натуральное условное распределение 3/4/5 скаттеров. Цена откалибрована симулятором: EV покупки ≈ 25.5× ставки → RTP покупки 94.61% (классика) / 94.11% (каскады) — в полосе цели. Кнопка с подтверждением, цена видна до покупки.

💎 Демо-джекпот

Общий прогрессивный промо-пул поверх RTP тем: растёт на 2% каждой ставки всех игроков (бухгалтерия казино — у игрока ничего не списывается), сид 500, потолок 5000. Шанс дропа растёт с пулом: p = 1/D, D = clamp(CAP·100/pool, 2, 2000); у потолка — гарантия. Атомарный клейм исключает двойную выплату при одновременных спинах. Выигрыш не входит в totalWin и лидерборд.

🏆 Лидерборд дня + соц. слой

Топ-10 за день по множителю лучшего раунда: score = (totalWin + риск-игра) / (ставка | цена бонуса); один игрок — одна строка; своё место вне топа приходит отдельной строкой. Ники — анонимные, стабильные, из хеша userId («Шериф-742»), без персональных данных. Сброс в полночь. Плюс: история раундов 🕘, ачивки-тосты, анлок премиум-звука за демо-кредиты, автоплей со стоп-условиями, турбо, fullscreen, мобильный портрет.

06RTP и калибровка

ТемаMathМеханикаRTP @1M (seed 777)HitВолатильностьBuy-bonus RTP
Wild Patrikv1.1.0классика, 10 линий95.20%27.33%4.19 (medium)94.61% @27×
Centavrav2.0.0каскады ×1/2/3/594.85%27.33%4.32 (medium)94.11% @27×
  • Один и тот же модуль математики используют /api/spin и CLI-симулятор — отчёт по определению соответствует продакшн-исходам.
  • Критерий приёмки: RTP в пределах 95% ± 1.5 п.п. на 1M сидированных спинов; CLI сам завершается с FAIL при выходе за порог. Для цены бонуса — отдельный критерий (--force-bonus).
  • Отчёты пишутся в reports/rtp-*.json|md (распределение множителей, топ-выигрыши, частота FS, σ).
  • Волатильность = σ множителя раунда: <4 low, 4–9 medium, >9 high.
  • Железное правило: после любой правки themes/<тема>/math/*.json — оба прогона для этой темы обязательны (см. AGENTS.md).

07Что можно менять (точки конфигурации)

📐 Математика темы — themes/<id>/math/*.json

Файл / полеЧто регулируетПосле правки
reels.jsonСостав лент = вероятности символов (главный регулятор RTP/волатильности)оба RTP-прогона
обязательны
paytable.jsonВыплаты за серии символов (множители ставки на линию)
gameConfig.json: scatterPays, freeSpins, freeSpinMultiplier, maxWinX, maxFreeSpinsPerRoundСкаттеры, фриспины, кап
gameConfig.json: buyBonus.priceX · cascades.multipliersЦена покупки бонуса · лестница каскадов (убрать поле = выключить фичу)
gameConfig.json: allowedBets, defaultBetЛинейка ставок (каждая должна делиться на 10 линий)достаточно ci
reelsFs.json (опционально)Отдельные «утяжелённые» ленты для фриспиновоба RTP-прогона

При изменении математики поднимайте version — она пишется в каждый раунд (mathVersion), что позволяет разбирать старые споры по старым правилам.

⚙️ Константы в коде

spin/service.ts
START_BALANCE (1000), UNLOCK_PRICES (premium_sound 300)
math/gamble.ts
GAMBLE_MAX_STEPS (5)
server/jackpot.ts
JACKPOT_SEED (500), JACKPOT_CAP (5000), взнос 2%
server/rateLimit.ts
лимиты спинов/IP/риск-игры
server/leaderboard.ts
топ-10, кеш 15 с, словарь ников

🎨 Темы и подача

  • Новая тема = каталог themes/<id>/ с manifest + math + арт; лобби и ?theme= подхватывают автоматически; ассеты проверяет validate:assets.
  • Звук: синтез-пак бесплатно; сэмплы — themes/<id>/sounds/*.ogg (CC0) за анлок.
  • Атлас символов и webp-фоны собирает scripts/build-atlas.ts (12 файлов → 1 запрос).
  • Структуру/имена файлов themes/wild-patrik/ не менять — внешний сертификационный контракт (113 файлов).

08«Стратегии» игрока — честный разбор

Слот — игра с фиксированным отрицательным матожиданием для игрока (RTP ≈ 95% < 100%): не существует стратегии, обыгрывающей дистанцию. Исход каждого спина независим, «горячих/холодных» полос нет. Что игрок реально контролирует:

РешениеЭффект на матожиданиеЭффект на дисперсию / опыт
Размер ставкине меняет RTPмасштабирует и выигрыши, и проигрыши
Риск-игра ×2нейтрально (честные 50/50)резко повышает дисперсию; «лесенка» до ×32
Buy BonusRTP покупки ~94.1–94.6% — чуть ниже базовой игрыгарантированное событие, концентрированная дисперсия
Выбор темы95.20% vs 94.85% — практически равныкаскады дают более «цепочечные» выигрыши
Автоплей-стопыне меняетдисциплина: стоп на фриспинах / большом выигрыше
Тайминг спина у джекпотапул у потолка → шанс растёт (промо-пул)единственный «+EV момент», и он намеренно прозрачен в тикере

Этот раздел — также чек-лист честности продукта: всё перечисленное видно игроку (цены, шансы, тикер), ничего не скрыто. В демо проигрыш = ноль реальных денег; новая сессия бесплатно даёт 1000 кредитов.

09Тесты и гарантии качества

  • Эталон зелёного состояния: build ✓ · 66/66 unit · 25/25 e2e · lint чистый. Любая правка — прогон до отчёта.
  • Что проверяется: консервация кредитов (баланс = ставка/цена − списания + выигрыши, на каждом шаге каждой фичи); детерминизм исходов от сида; консистентность каскадных цепочек (окно шага N+1 = рефилл шага N, финал без выигрышей); невозможность фейкового выигрыша с клиента; гарантия дропа джекпота у потолка; гонки (параллельные gamble/джекпот-клеймы); честность 50/50 риск-игры и цены бонуса против живого конфига.
  • Изоляция от прода: e2e ходят на :3007 и прод не трогают; dev и prod собираются в разные каталоги (.next-dev/.next) — dev-сервер не может «съесть» BUILD_ID прода.
  • Если до твоей правки что-то красное — стоп и доклад, чужое молча не чинить (правило AGENTS.md).

10Структура проекта

slot-engine/
├─ src/server/math/        # ЧИСТАЯ математика: rng, reels, paylines, evaluateSpin,
│                          # cascade, gamble, playRound, simulateRtp (server-only)
├─ src/server/             # spin/service (транзакции), jackpot, leaderboard, rateLimit, db
├─ src/app/api/            # session, spin, gamble, unlock, history, leaderboard, jackpot, themes
├─ src/app/demo/           # игровая страница (React, презентация исходов)
├─ src/app/admin/          # админка (дашборд, раунды, симуляции, конфиги, ошибки)
├─ src/game/engine/        # Pixi: SlotRenderer, ReelController, AnimationController, SymbolMap
├─ src/game/sound/         # WebAudio: синтез + сэмпл-пак
├─ src/middleware.ts       # HMAC-защита админки (edge-runtime! только crypto.subtle)
├─ themes/<id>/            # manifest, math/*.json, арт, звуки — контракт темы
├─ scripts/                # simulate-rtp, calibrate-math, validate-assets, build-atlas, sync-themes
├─ prisma/                 # схема + миграции (users/sessions/balances/rounds/audit/jackpot)
├─ tests/  e2e/            # vitest + Playwright
└─ README ARCHITECTURE MATH_MODEL SECURITY ACCEPTANCE DEPLOYMENT AGENTS LICENSES

Документы-первоисточники: AGENTS.md (правила для агентов и эталоны), MATH_MODEL.md (модель и симуляции), SECURITY.md (границы демо), ACCEPTANCE.md (критерии приёмки 12/12).

11Как улучшать игру дальше

Развёрнутая версия с привязкой «фича → какие ассеты генерировать», стандартами рекламных форматов (push-сети класса Kadam, баннеры, видео) и ТЗ-промптами — в парном документе /production.html.

🎮 Геймплей

  • Третья тема под каскадную математику (движок готов — нужен только арт-пак).
  • Ways-pays / Megaways: выплаты «по способам» вместо линий; переменная высота барабанов.
  • Кластерные выплаты (5+ смежных символов) — естественное продолжение каскадов.
  • Уровневый джекпот mini/major/grand с разными порогами.
  • Турниры по времени: окно 1 час, очки = множители, призы демо-кредитами (лидерборд уже есть).
  • Миссии/квесты: «поймай 3 фриспина за день» → кредиты; постоянные ачивки в профиле (сейчас — тосты на сессию).
  • Риск-игра «лесенка мастей» (угадай масть = ×4) как второй режим гэмбла.

✨ Подача и UX

  • Полировка каскадной анимации по живому впечатлению (физика падения по спрайтам, частицы при лопании).
  • Реплей раунда по ссылке: RoundResult уже хранит всё — страница «посмотреть как я взял ×212» + шэринг на лидерборде.
  • Записанная музыка и расширенный сэмпл-пак (фаза 2 — сейчас синтез + CC0-сэмплы).
  • Анте-бет (ставка +25% → шанс FS ×2) — стандартная опция жанра, считается тем же симулятором.
  • PWA: установка на телефон, оффлайн-заставка.
  • Локализация EN (сейчас RU-only).

🛠 Техника

  • Valkey/механизм вне процесса для rate-limit и кешей при переходе на несколько инстансов (интерфейсы уже выделены).
  • A/B-тест математики: две версии MathConfig параллельно, сравнение фактического RTP в админке (mathVersion уже пишется в каждый раунд).
  • Сбор клиентских ошибок (Sentry-класс) и FPS-метрики рендера.
  • Капча/механика на создание сессий, если демо выйдет в полностью открытый интернет.

🧭 Принципы при любом развитии

  • Исходы — только сервер; src/game/** не импортирует @/server/**.
  • Деньги/крипта — никогда (демо-граница из SECURITY.md); «платность» = демо-кредиты.
  • Новая механика = новая версия математики + перекалибровка + оба RTP-прогона + тесты до эталона.
  • Тонкие проверяемые срезы: фича → ci+e2e зелёные → коммит → деплой → смоук.