Specification · algorithms · administration · development guide
| URL | What is this |
|---|---|
| / | Lobby: theme cards, 🏆 daily leaders, 💎 jackpot |
| /demo | Game, default theme — Wild Patrik (classic) |
| /demo?theme=centavra | Centavra — sci-fi theme with cascades |
ℹ️ Centavra here — second theme of the same engine
(catalog themes/centavra/: own art + own math v2.0.0), not a separate project.
Do not confuse with the eponymous project centavra-mission-spin (bottle/, ports :3005/:5002) — that one is not related to slot-engine. | |
| /admin | Admin panel (password, see section 02) |
| /spec.html | This document |
| /production.html | Production & promo: standards for ad assets, briefs for generation, roadmap |
| /investors.html | For investors: two-model pipeline (Opus 4.8 × Fable 5), who built what, remaining Roadmap |
| /strategy.html | Exit strategy: market, monetization (USDT vs license), blockchain/provably-fair, geography, phased de-risking |
| /investor-deck.html | Investor deck: 3 screens 16:9 (PDF: /slot-engine-investor-deck.pdf) |
http://127.0.0.1:3006 — pm2 slot-engine, catalog .nexthttp://127.0.0.1:3007 — npm run dev, catalog .next-devslot_engine (password in .env).env — outside git; do not read, do not printNever occupy :3006 dev-server and do not delete .next while pm2 is running.
Restart of production — pm2 restart slot-engine only after npm run build.
cd slot-engine
npm run dev # dev server :3007
npm run ci # lint + 66 unit + production build (green = can commit)
npm run e2e # 25 Playwright tests (auto-starts dev server)
npm run build && pm2 restart slot-engine # deploy on :3006
npm run simulate:rtp -- --config wild-patrik --spins 1000000 --seed 777 # RTP report
npm run simulate:rtp -- --config centavra --spins 200000 --seed 777 --force-bonus # bonus price
npm run validate:assets -- --theme wild-patrik # asset contract
Login: open /admin → redirect to /admin/login → enter password.
Password is stored in the variable ADMIN_PASSWORD file .env on the server (not in this document
or in git — it's a secret). After login, a signed HMAC cookie is set; middleware closes
all /admin/* and /api/math/*; if the password is not set in .env — admin panel responds with 503.
| Section | What it can do |
|---|---|
/admin | Dashboard: daily statistics (spins, bets, wins, actual RTP), top wins |
/admin/rounds | All rounds with pagination: bet, win, scatters, FS, cap |
/admin/rounds/[id] | Dispute resolution: full round outcome with grid images — base window, each line, free spin chain, cascades |
/admin/simulations | RTP simulation history + «Run simulation» button (theme + number of spins, live result) |
/admin/configs | Math versions of themes (what is recorded in the DB during spins) |
/admin/validation | Asset validator reports |
/admin/errors | Error log from audit_logs (type=error) |
⚠️ Known gotcha: parallel IDE agents broke twice
src/middleware.ts, inserting node:crypto — middleware runs in edge-runtime,
only available crypto.subtle. If build fails with «Unhandled scheme node:» — check this file first.
| Endpoint | Purpose | |
|---|---|---|
| POST | /api/session | New anonymous session, starting balance 1000 demo credits |
| GET | /api/session?sessionId= | State: balance, purchased unlocks |
| POST | /api/spin | Spin: {sessionId, bet, themeId, buyBonus?} → window, lines, free spins, cascades, jackpot, balance. Single source of outcomes. |
| POST | /api/gamble | Risk game ×2: {sessionId, roundId, action: red|black|collect} |
| POST | /api/unlock | Feature purchase with demo credits (e.g. premium_sound = 300) |
| GET | /api/history?sessionId= | Player's recent rounds (modal 🕘) |
| GET | /api/leaderboard?sessionId= | 🏆 Top 10 of the day + your position (sessionId optional) |
| GET | /api/jackpot | 💎 Current promo pool for ticker |
| GET | /api/themes | Theme list: bets, default bet |
| ADMIN | /api/math/simulate | Launch simulation from admin panel (middleware closed) |
All mutations are behind rate-limit (token-bucket: 3 spins/s per session, 10/s per IP). Extra fields in request body are discarded by zod schemas — it's impossible to "pass desired outcome" from the client (there is an e2e test exactly for this).
crypto.randomInt (OS CSPRNG, no modulo-bias). No Math.random in outcomes.src/server/math with package server-only — frontend physically cannot import it.Round + full RoundResult.detail (reels, lines, freespins, cascades) — any dispute is resolved in admin panel via screenshot.reelsFs.json, optional)RTP is adjusted by reel strip composition (how many times each symbol appears
on each reel — the COUNTS table in scripts/calibrate-math.ts) and the paytable values. The probabilities
honestly follow from the reels — no "tweaking" of the outcome after generation.
Enabling cascades raised the "raw" RTP to 133.78% — Centavra's paytable recalibrated (×0.71) back to target: 94.85% @1M. Wild Patrik untouched: control run exactly 95.20%.
After any win: guess the card color — win doubles, wrong guess — lost.
Up to 5 steps (then auto-collect). Fair 50/50 on crypto-RNG,
EV-neutral — does not change game RTP, only variance.
The outcome is decided by the server (POST /api/gamble); the card (suit/rank) is a derivative of the outcome for display.
A new spin = silent collect. Race protection — optimistic guard by step number.
Instant entry into free spins for 27× bet. Implementation — rejection-sampling: the server re-rolls stops until 3+ scatters, which preserves the natural conditional distribution of 3/4/5 scatters. Price calibrated by simulator: Buy EV ≈ 25.5× bet → Buy RTP 94.61% (classic) / 94.11% (cascades) — within target band. Button with confirmation, price visible before purchase.
Shared progressive promo pool on top of RTP themes: grows by 2% of each bet from all players
(casino accounting — nothing deducted from the player), seed 500, cap 5000.
Drop chance increases with pool: p = 1/D, D = clamp(CAP·100/pool, 2, 2000); at cap — guaranteed.
Atomic claim prevents double payout on simultaneous spins. Win not included in totalWin or leaderboard.
Top-10 for the day by best round multiplier: score = (totalWin + risk game) / (bet | bonus price);
one player — one row; own position outside top comes as a separate row. Nicknames — anonymous, stable,
from userId hash («Sheriff-742»), no personal data. Reset at midnight. Plus: round history 🕘,
achievement toasts, premium sound unlock for demo credits, autoplay with stop conditions, turbo, fullscreen, mobile portrait.
| Theme | Math | Mechanics | RTP @1M (seed 777) | Hit | Volatility | Buy-bonus RTP |
|---|---|---|---|---|---|---|
| Wild Patrik | v1.1.0 | classic, 10 lines | 95.20% | 27.33% | 4.19 (medium) | 94.61% @27× |
| Centavra | v2.0.0 | cascades ×1/2/3/5 | 94.85% | 27.33% | 4.32 (medium) | 94.11% @27× |
/api/spin and the CLI simulator — the report is guaranteed to match production outcomes.--force-bonus).reports/rtp-*.json|md (multiplier distribution, top wins, FS frequency, σ).themes/<theme>/math/*.json — both runs for this theme are mandatory (see AGENTS.md).themes/<id>/math/*.json| File / field | What regulates | After editing |
|---|---|---|
reels.json | Reel strips = symbol probabilities (main RTP/volatility regulator) | both RTP runs mandatory |
paytable.json | Payouts for symbol series (line bet multipliers) | |
gameConfig.json: scatterPays, freeSpins, freeSpinMultiplier, maxWinX, maxFreeSpinsPerRound | Scatters, free spins, cap | |
gameConfig.json: buyBonus.priceX · cascades.multipliers | Bonus buy price · cascade ladder (remove field = disable feature) | |
gameConfig.json: allowedBets, defaultBet | Bet line (each must be divisible by 10 lines) | ci is enough |
reelsFs.json (optional) | Separate 'weighted' strips for free spins | both RTP runs |
When changing math, bump version — it is written into each round (mathVersion), allowing old disputes to be resolved by old rules.
spin/service.tsSTART_BALANCE (1000), UNLOCK_PRICES (premium_sound 300)math/gamble.tsGAMBLE_MAX_STEPS (5)server/jackpot.tsJACKPOT_SEED (500), JACKPOT_CAP (5000), 2% feeserver/rateLimit.tsserver/leaderboard.tsthemes/<id>/ with manifest + math + art; lobby and ?theme= pick up automatically; assets are checked validate:assets.themes/<id>/sounds/*.ogg (CC0) for unlock.scripts/build-atlas.ts (12 files → 1 request).themes/wild-patrik/ do not change — external certification contract (113 files).Slot is a game with a fixed negative mathematical expectation for the player (RTP ≈ 95% < 100%): there is no strategy that beats the distance. The outcome of each spin is independent, there are no “hot/cold” streaks. What the player actually controls:
| Decision | Effect on mathematical expectation | Effect on variance / experience |
|---|---|---|
| Bet size | does not change RTP | scales both wins and losses |
| Risk game ×2 | neutral (fair 50/50) | sharply increases variance; ladder up to ×32 |
| Buy Bonus | purchase RTP ~94.1–94.6% — slightly below base game | guaranteed event, concentrated variance |
| Theme selection | 95.20% vs 94.85% — practically equal | cascades yield more chain-like wins |
| Autoplay stops | does not change | discipline: stop on free spins / big win |
| Jackpot spin timing | pool near ceiling → chance increases (promo pool) | the only '+EV moment', and it is deliberately transparent in the ticker |
This section is also a product honesty checklist: everything listed is visible to the player (prices, odds, ticker), nothing is hidden. In demo, loss = zero real money; a new session gives 1000 credits for free.
.next-dev/.next) — the dev server cannot 'eat' prod's BUILD_ID.slot-engine/
├─ src/server/math/ # PURE math: rng, reels, paylines, evaluateSpin,
│ # cascade, gamble, playRound, simulateRtp (server-only)
├─ src/server/ # spin/service (transactions), jackpot, leaderboard, rateLimit, db
├─ src/app/api/ # session, spin, gamble, unlock, history, leaderboard, jackpot, themes
├─ src/app/demo/ # game page (React, outcome presentation)
├─ src/app/admin/ # admin (dashboard, rounds, simulations, configs, errors)
├─ src/game/engine/ # Pixi: SlotRenderer, ReelController, AnimationController, SymbolMap
├─ src/game/sound/ # WebAudio: synthesis + sample pack
├─ src/middleware.ts # HMAC-protected admin (edge-runtime! only crypto.subtle)
├─ themes/<id>/ # manifest, math/*.json, art, sounds — theme contract
├─ scripts/ # simulate-rtp, calibrate-math, validate-assets, build-atlas, sync-themes
├─ prisma/ # schema + migrations (users/sessions/balances/rounds/audit/jackpot)
├─ tests/ e2e/ # vitest + Playwright
└─ README ARCHITECTURE MATH_MODEL SECURITY ACCEPTANCE DEPLOYMENT AGENTS LICENSES
Source documents: AGENTS.md (rules for agents and standards),
MATH_MODEL.md (model and simulations), SECURITY.md (demo boundaries), ACCEPTANCE.md (acceptance criteria 12/12).
Extended version with 'feature → which assets to generate' mapping, standards for ad formats (push networks like Kadam, banners, video) and spec-prompts — in the paired document /production.html.
src/game/** does not import @/server/**.