lobby: 20 hand-composed game posters (symbol-svg mirrors reel art); crash engine + 10 risk game definitions certified; crash_rounds table
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
24 changed files +6,600 −55
modified
apps/simulator/src/cli.ts
+14 −3
@@ -10,8 +10,8 @@ | ||
| 10 | 10 | import fs from "node:fs"; |
| 11 | 11 | import path from "node:path"; |
| 12 | 12 | import { fileURLToPath } from "node:url"; |
| 13 | −import { certify, formatCertification, validateDefinition, DEFAULT_CERTIFICATION_RULES } from "@spinza/game-core"; | |
| 14 | −import { RAW_GAMES, GAMES } from "@spinza/games"; | |
| 13 | +import { certify, certifyCrash, formatCertification, simulateCrash, validateDefinition, DEFAULT_CERTIFICATION_RULES } from "@spinza/game-core"; | |
| 14 | +import { RAW_GAMES, GAMES, CRASH_GAMES, CRASH_BY_SLUG } from "@spinza/games"; | |
| 15 | 15 | import { calibrate, simulateParallel } from "./index"; |
| 16 | 16 | |
| 17 | 17 | const here = path.dirname(fileURLToPath(import.meta.url)); |
@@ -94,7 +94,18 @@ async function main() { | ||
| 94 | 94 | fs.mkdirSync(certDir, { recursive: true }); |
| 95 | 95 | const spins = num("spins", 1_000_000); |
| 96 | 96 | let failed = 0; |
| 97 | − for (const slug of targets(sel)) { | |
| 97 | + const list = sel === "crash" ? CRASH_GAMES.map((g) => g.slug) : targets(sel); | |
| 98 | + for (const slug of list) { | |
| 99 | + const crashDef = CRASH_BY_SLUG.get(slug); | |
| 100 | + if (crashDef) { | |
| 101 | + console.log(`\nCertifying ${crashDef.name} (${slug}@${crashDef.version}, crash) — ${spins.toLocaleString("en-US")} rounds`); | |
| 102 | + const res = simulateCrash(crashDef, { spins }); | |
| 103 | + const report = certifyCrash(crashDef, res, { ...DEFAULT_CERTIFICATION_RULES, minSpins: Math.min(DEFAULT_CERTIFICATION_RULES.minSpins, spins) }); | |
| 104 | + fs.writeFileSync(path.join(certDir, `${slug}.json`), JSON.stringify(report, null, 2) + "\n"); | |
| 105 | + console.log(formatCertification(report)); | |
| 106 | + if (report.status === "FAIL") failed++; | |
| 107 | + continue; | |
| 108 | + } | |
| 98 | 109 | const g = GAMES.find((x) => x.slug === slug)!; |
| 99 | 110 | console.log(`\nCertifying ${g.name} (${slug}@${g.version}) payScale=${g.payScale} — ${spins.toLocaleString("en-US")} spins`); |
| 100 | 111 | const res = await simulateParallel(slug, { spins, payScale: g.payScale, threads, onProgress: progressBar }); |
modified
apps/web/src/components/lobby/game-art.tsx
+50 −50
@@ -94,7 +94,7 @@ const DIMS: Record<ArtVariant, { w: number; h: number }> = { | ||
| 94 | 94 | function focal(v: ArtVariant, W: number, H: number, showName: boolean): { cx: number; cy: number; R: number } { |
| 95 | 95 | switch (v) { |
| 96 | 96 | case "card": |
| 97 | − return { cx: W / 2, cy: showName ? H * 0.41 : H * 0.5, R: W * 0.42 }; | |
| 97 | + return { cx: W / 2, cy: showName ? H * 0.385 : H * 0.5, R: W * 0.405 }; | |
| 98 | 98 | case "tile": |
| 99 | 99 | return { cx: W / 2, cy: H / 2, R: W * 0.4 }; |
| 100 | 100 | case "hero": |
@@ -212,7 +212,7 @@ function NeonVault(s: Stage) { | ||
| 212 | 212 | <Sym s={s} id="V" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.4} rotate={-8} /> |
| 213 | 213 | <rect x={f(cx - R * 1.1)} y={f(scanY - R * 0.05)} width={f(R * 2.2)} height={f(R * 0.1)} fill={`url(#${id}-nv3)`} /> |
| 214 | 214 | <line x1={f(cx - R * 1.1)} y1={f(scanY)} x2={f(cx + R * 1.1)} y2={f(scanY)} stroke={p.secondary} strokeWidth={1.5} strokeOpacity={0.9} /> |
| 215 | − <Small x={cx} y={cy + R * 1.18} text="×2 ×3 ×5 ×8 ×12 ×20 ×30 ×50" color={p.primary} size={R * 0.075} tracking="0.18em" opacity={0.7} /> | |
| 215 | + <Small x={cx} y={cy + R * 1.0} text="×2 ×3 ×5 ×8 ×12 ×20 ×30 ×50" color={p.primary} size={R * 0.075} tracking="0.18em" opacity={0.7} /> | |
| 216 | 216 | </> |
| 217 | 217 | ) : null} |
| 218 | 218 | </g> |
@@ -257,7 +257,7 @@ function CosmicCollapse(s: Stage) { | ||
| 257 | 257 | ? orbit.map((o) => <Sym key={o.id} s={s} id={o.id} x={cx + Math.cos(o.a) * R * o.d} y={cy + Math.sin(o.a) * R * o.d * 0.72} size={R * o.k} rotate={o.rot} />) |
| 258 | 258 | : null} |
| 259 | 259 | {!compact ? ( |
| 260 | − <text x={f(cx)} y={f(cy + R * 1.22)} textAnchor="middle" dominantBaseline="central" fontWeight={700} fill={p.primary} style={{ fontFamily: FONT, letterSpacing: "0.1em" }}> | |
| 260 | + <text x={f(cx)} y={f(cy + R * 1.0)} textAnchor="middle" dominantBaseline="central" fontWeight={700} fill={p.primary} style={{ fontFamily: FONT, letterSpacing: "0.1em" }}> | |
| 261 | 261 | <tspan fontSize={f(R * 0.07)} fillOpacity={0.35}>×1 ×2 ×3 ×5 ×8 </tspan> |
| 262 | 262 | <tspan fontSize={f(R * 0.11)} fill={p.glow} fillOpacity={0.9}>×13</tspan> |
| 263 | 263 | </text> |
@@ -279,7 +279,7 @@ function GoldenEmperor(s: Stage) { | ||
| 279 | 279 | { x: cx, m: "×3" }, |
| 280 | 280 | { x: cx + R * 0.66, m: "×5" }, |
| 281 | 281 | ]; |
| 282 | − const py = cy + R * 0.62; | |
| 282 | + const py = cy + R * 0.55; | |
| 283 | 283 | return ( |
| 284 | 284 | <g> |
| 285 | 285 | <defs> |
@@ -301,7 +301,7 @@ function GoldenEmperor(s: Stage) { | ||
| 301 | 301 | {pearls.map((o) => ( |
| 302 | 302 | <g key={o.m}> |
| 303 | 303 | <Sym s={s} id="W" x={o.x} y={py} size={R * 0.5} /> |
| 304 | − <Pill x={o.x} y={py + R * 0.38} text={o.m} color={p.primary} ink={p.glow} size={R * 0.085} /> | |
| 304 | + <Pill x={o.x} y={py + R * 0.36} text={o.m} color={p.primary} ink={p.glow} size={R * 0.085} /> | |
| 305 | 305 | </g> |
| 306 | 306 | ))} |
| 307 | 307 | <Sym s={s} id="S" x={cx - R * 0.7} y={cy - R * 0.4} size={R * 0.4} /> |
@@ -332,9 +332,9 @@ function DiamondHeist(s: Stage) { | ||
| 332 | 332 | const lasers = `M${f(cx - R * 1.4)} ${f(cy - R * 1.3)}L${f(cx + R * 1.4)} ${f(cy + R * 0.2)} M${f(cx - R * 1.4)} ${f(cy + R * 0.9)}L${f(cx + R * 1.4)} ${f(cy - R * 1.1)}`; |
| 333 | 333 | const tiers = ["MINI", "MINOR", "MAJOR", "GRAND"]; |
| 334 | 334 | const coins = [ |
| 335 | − { x: cx - R * 0.72, y: cy + R * 0.62, v: "5×" }, | |
| 336 | − { x: cx, y: cy + R * 0.8, v: "20×" }, | |
| 337 | − { x: cx + R * 0.72, y: cy + R * 0.62, v: "10×" }, | |
| 335 | + { x: cx - R * 0.72, y: cy + R * 0.5, v: "5×" }, | |
| 336 | + { x: cx, y: cy + R * 0.66, v: "20×" }, | |
| 337 | + { x: cx + R * 0.72, y: cy + R * 0.5, v: "10×" }, | |
| 338 | 338 | ]; |
| 339 | 339 | return ( |
| 340 | 340 | <g> |
@@ -360,16 +360,16 @@ function DiamondHeist(s: Stage) { | ||
| 360 | 360 | {coins.map((c) => ( |
| 361 | 361 | <g key={c.v}> |
| 362 | 362 | <Sym s={s} id="D" x={c.x} y={c.y} size={R * 0.4} /> |
| 363 | − <Pill x={c.x} y={c.y + R * 0.3} text={c.v} color={p.primary} ink={p.glow} size={R * 0.08} /> | |
| 363 | + <Pill x={c.x} y={c.y + R * 0.28} text={c.v} color={p.primary} ink={p.glow} size={R * 0.08} /> | |
| 364 | 364 | </g> |
| 365 | 365 | ))} |
| 366 | 366 | {tiers.map((t, i) => { |
| 367 | − const y = cy - R * 1.15 + i * R * 0.27; | |
| 367 | + const y = cy - R * 0.62 + i * R * 0.3; | |
| 368 | 368 | const grand = i === 3; |
| 369 | 369 | return <Pill key={t} x={cx + R * 1.02} y={y} text={t} color={grand ? "#ffd166" : p.secondary} ink={grand ? "#ffd166" : "#dfe4ea"} bg={grand ? "#2a2008" : "#000"} size={R * 0.075} w={R * 0.5} />; |
| 370 | 370 | })} |
| 371 | − <Sym s={s} id="W" x={cx - R * 0.98} y={cy - R * 0.55} size={R * 0.5} rotate={-30} /> | |
| 372 | − <Sym s={s} id="P1" x={cx - R * 1.0} y={cy - R * 1.08} size={R * 0.38} /> | |
| 371 | + <Sym s={s} id="W" x={cx - R * 1.0} y={cy - R * 0.62} size={R * 0.5} rotate={-30} /> | |
| 372 | + <Sym s={s} id="P1" x={cx + R * 1.0} y={cy - R * 1.0} size={R * 0.38} /> | |
| 373 | 373 | </> |
| 374 | 374 | ) : null} |
| 375 | 375 | <path d={sparkles} fill="#fff" fillOpacity={0.85} /> |
@@ -416,7 +416,7 @@ function ArcticFortune(s: Stage) { | ||
| 416 | 416 | <Sym s={s} id="P1" x={cx + R * 0.72} y={cy + R * 0.5} size={R * 0.62} /> |
| 417 | 417 | <Sym s={s} id="H1" x={cx - R * 0.78} y={cy - R * 0.45} size={R * 0.44} /> |
| 418 | 418 | <Sym s={s} id="S" x={cx + R * 0.1} y={cy - R * 0.7} size={R * 0.38} /> |
| 419 | − <Small x={cx} y={cy + R * 1.22} text="×1 ×2 ×3 ×4 ×5" color="#e8f8ff" size={R * 0.08} tracking="0.16em" opacity={0.75} /> | |
| 419 | + <Small x={cx} y={cy + R * 1.0} text="×1 ×2 ×3 ×4 ×5" color="#e8f8ff" size={R * 0.08} tracking="0.16em" opacity={0.75} /> | |
| 420 | 420 | </> |
| 421 | 421 | ) : ( |
| 422 | 422 | <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} /> |
@@ -472,7 +472,7 @@ function InfernoReels(s: Stage) { | ||
| 472 | 472 | <Sym s={s} id="S" x={cx + R * 0.92} y={cy - R * 0.75} size={R * 0.46} rotate={16} /> |
| 473 | 473 | <Sym s={s} id="H1" x={cx - R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={22} /> |
| 474 | 474 | <Sym s={s} id="H2" x={cx + R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={-20} /> |
| 475 | − <Small x={cx} y={cy + R * 1.2} text="×2 · ×3 · ×5 · ×10" color="#ffd166" size={R * 0.085} tracking="0.16em" weight={700} opacity={0.9} /> | |
| 475 | + <Small x={cx} y={cy + R * 1.0} text="×2 · ×3 · ×5 · ×10" color="#ffd166" size={R * 0.085} tracking="0.16em" weight={700} opacity={0.9} /> | |
| 476 | 476 | </> |
| 477 | 477 | ) : null} |
| 478 | 478 | <path d={embers} fill={p.glow} fillOpacity={0.8} /> |
@@ -531,7 +531,7 @@ function QuantumJackpot(s: Stage) { | ||
| 531 | 531 | <Sym s={s} id="W" x={cx - R * 0.85} y={cy - R * 0.85} size={R * 0.48} /> |
| 532 | 532 | <Sym s={s} id="H1" x={cx + R * 0.85} y={cy - R * 0.9} size={R * 0.44} /> |
| 533 | 533 | <Sym s={s} id="S" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.4} /> |
| 534 | − <Small x={cx} y={cy + R * 1.25} text="MINI 20× · MINOR 50× · MAJOR 200× · GRAND 1,000×" color={p.glow} size={R * 0.062} tracking="0.1em" opacity={0.8} /> | |
| 534 | + <Small x={cx} y={cy + R * 1.0} text="MINI 20× · MINOR 50× · MAJOR 200× · GRAND 1,000×" color={p.glow} size={R * 0.062} tracking="0.1em" opacity={0.8} /> | |
| 535 | 535 | </> |
| 536 | 536 | ) : null} |
| 537 | 537 | </g> |
@@ -603,8 +603,8 @@ function MidnightTokyo(s: Stage) { | ||
| 603 | 603 | /* 09 — PHARAOH PROTOCOL: pyramid with a scanning laser, artifact meter */ |
| 604 | 604 | function PharaohProtocol(s: Stage) { |
| 605 | 605 | const { W, H, cx, cy, R, id, p, compact } = s; |
| 606 | − const base = cy + R * 0.85; | |
| 607 | − const apex = cy - R * 0.85; | |
| 606 | + const base = cy + R * 0.7; | |
| 607 | + const apex = cy - R * 0.98; | |
| 608 | 608 | const half = R * 1.05; |
| 609 | 609 | const tri = `${f(cx - half)},${f(base)} ${f(cx)},${f(apex)} ${f(cx + half)},${f(base)}`; |
| 610 | 610 | let bricks = ""; |
@@ -614,7 +614,7 @@ function PharaohProtocol(s: Stage) { | ||
| 614 | 614 | const segs = 12; |
| 615 | 615 | const mw = R * 1.4; |
| 616 | 616 | const mx = cx - mw / 2; |
| 617 | − const my = cy + R * 1.12; | |
| 617 | + const my = cy + R * 0.86; | |
| 618 | 618 | let segOff = ""; |
| 619 | 619 | let segOn = ""; |
| 620 | 620 | for (let i = 0; i < segs; i++) { |
@@ -633,8 +633,8 @@ function PharaohProtocol(s: Stage) { | ||
| 633 | 633 | </clipPath> |
| 634 | 634 | </defs> |
| 635 | 635 | <rect width={W} height={H} fill={`url(#${id}-pp1)`} /> |
| 636 | − <circle cx={f(cx + R * 0.75)} cy={f(cy - R * 0.72)} r={f(R * 0.55)} fill={`url(#${id}-pp3)`} /> | |
| 637 | − <circle cx={f(cx + R * 0.75)} cy={f(cy - R * 0.72)} r={f(R * 0.2)} fill="#fb923c" fillOpacity={0.9} /> | |
| 636 | + <circle cx={f(cx + R * 0.78)} cy={f(cy - R * 0.8)} r={f(R * 0.55)} fill={`url(#${id}-pp3)`} /> | |
| 637 | + <circle cx={f(cx + R * 0.78)} cy={f(cy - R * 0.8)} r={f(R * 0.2)} fill="#fb923c" fillOpacity={0.9} /> | |
| 638 | 638 | <ellipse cx={f(cx - R * 0.6)} cy={f(base + R * 0.15)} rx={f(W * 0.7)} ry={f(R * 0.3)} fill="#3a2810" /> |
| 639 | 639 | <ellipse cx={f(cx + R * 0.9)} cy={f(base + R * 0.25)} rx={f(W * 0.6)} ry={f(R * 0.28)} fill="#2a1c0a" /> |
| 640 | 640 | <polygon points={tri} fill={`url(#${id}-pp2)`} /> |
@@ -645,14 +645,14 @@ function PharaohProtocol(s: Stage) { | ||
| 645 | 645 | <Sym s={s} id="W" x={cx} y={apex - R * 0.05} size={R * 0.72} plate={false} /> |
| 646 | 646 | {!compact ? ( |
| 647 | 647 | <> |
| 648 | − <Sym s={s} id="P1" x={cx + R * 0.62} y={cy + R * 0.35} size={R * 0.62} /> | |
| 649 | − <Sym s={s} id="H1" x={cx - R * 0.8} y={cy + R * 0.3} size={R * 0.5} /> | |
| 650 | − <Sym s={s} id="AR" x={cx - R * 0.55} y={cy + R * 0.72} size={R * 0.36} /> | |
| 651 | − <Sym s={s} id="AR" x={cx - R * 0.2} y={cy + R * 0.78} size={R * 0.36} /> | |
| 652 | − <Sym s={s} id="AR" x={cx + R * 0.15} y={cy + R * 0.72} size={R * 0.36} /> | |
| 648 | + <Sym s={s} id="P1" x={cx + R * 0.66} y={cy + R * 0.22} size={R * 0.62} /> | |
| 649 | + <Sym s={s} id="H1" x={cx - R * 0.82} y={cy + R * 0.18} size={R * 0.5} /> | |
| 650 | + <Sym s={s} id="AR" x={cx - R * 0.5} y={cy + R * 0.6} size={R * 0.36} /> | |
| 651 | + <Sym s={s} id="AR" x={cx - R * 0.15} y={cy + R * 0.64} size={R * 0.36} /> | |
| 652 | + <Sym s={s} id="AR" x={cx + R * 0.2} y={cy + R * 0.6} size={R * 0.36} /> | |
| 653 | 653 | <path d={segOff} fill="#fff" fillOpacity={0.12} /> |
| 654 | 654 | <path d={segOn} fill={p.secondary} fillOpacity={0.95} /> |
| 655 | − <Small x={cx} y={my + R * 0.16} text="ARTIFACT SCANNER · 7 / 12" color={p.secondary} size={R * 0.065} tracking="0.2em" opacity={0.85} /> | |
| 655 | + <Small x={cx} y={my + R * 0.15} text="ARTIFACT SCANNER · 7 / 12" color={p.secondary} size={R * 0.065} tracking="0.2em" opacity={0.85} /> | |
| 656 | 656 | </> |
| 657 | 657 | ) : null} |
| 658 | 658 | <path d={dust} fill={p.primary} fillOpacity={0.5} /> |
@@ -664,7 +664,7 @@ function PharaohProtocol(s: Stage) { | ||
| 664 | 664 | function LuckyCircuit(s: Stage) { |
| 665 | 665 | const { W, H, cx, cy, R, id, p, compact } = s; |
| 666 | 666 | const bw = R * 2.2; |
| 667 | − const bh = R * 2.05; | |
| 667 | + const bh = R * 1.9; | |
| 668 | 668 | const bx = cx - bw / 2; |
| 669 | 669 | const by = cy - bh / 2 - R * 0.05; |
| 670 | 670 | let scan = ""; |
@@ -698,13 +698,13 @@ function LuckyCircuit(s: Stage) { | ||
| 698 | 698 | <Sym s={s} id="MY" x={cx} y={cy - R * 0.08} size={R * 1.0} /> |
| 699 | 699 | {!compact ? ( |
| 700 | 700 | <> |
| 701 | − <Sym s={s} id="W" x={cx - R * 0.78} y={cy - R * 0.72} size={R * 0.48} rotate={-12} /> | |
| 702 | − <Sym s={s} id="P1" x={cx + R * 0.78} y={cy - R * 0.72} size={R * 0.48} rotate={10} /> | |
| 701 | + <Sym s={s} id="W" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.48} rotate={-12} /> | |
| 702 | + <Sym s={s} id="P1" x={cx + R * 0.78} y={cy - R * 0.5} size={R * 0.48} rotate={10} /> | |
| 703 | 703 | <Sym s={s} id="H1" x={cx - R * 0.78} y={cy + R * 0.6} size={R * 0.46} /> |
| 704 | 704 | <Sym s={s} id="M2" x={cx + R * 0.78} y={cy + R * 0.6} size={R * 0.46} /> |
| 705 | 705 | <Sym s={s} id="S" x={cx} y={cy + R * 0.72} size={R * 0.38} /> |
| 706 | − <rect x={f(cx - R * 0.55)} y={f(by - R * 0.42)} width={f(R * 1.1)} height={f(R * 0.24)} rx={f(R * 0.05)} fill={p.glow} /> | |
| 707 | − <Small x={cx} y={by - R * 0.3} text="INSERT COIN" color="#fff" size={R * 0.1} weight={800} tracking="0.2em" /> | |
| 706 | + <rect x={f(cx - R * 0.55)} y={f(by + R * 0.06)} width={f(R * 1.1)} height={f(R * 0.24)} rx={f(R * 0.05)} fill={p.glow} /> | |
| 707 | + <Small x={cx} y={by + R * 0.18} text="INSERT COIN" color="#fff" size={R * 0.1} weight={800} tracking="0.2em" /> | |
| 708 | 708 | </> |
| 709 | 709 | ) : null} |
| 710 | 710 | </g> |
@@ -731,8 +731,8 @@ function VoidMiner(s: Stage) { | ||
| 731 | 731 | [cx + R * 0.7, cy + R * 0.95, R * 0.3, 8], |
| 732 | 732 | [cx - R * 0.15, cy - R * 1.12, R * 0.16, 7], |
| 733 | 733 | ] as const; |
| 734 | − const drillX = cx + R * 0.72; | |
| 735 | − const drillY = cy - R * 0.72; | |
| 734 | + const drillX = cx + R * 0.6; | |
| 735 | + const drillY = cy - R * 0.58; | |
| 736 | 736 | const circ = 2 * PI * R * 0.66; |
| 737 | 737 | const seg = circ / 5; |
| 738 | 738 | return ( |
@@ -755,8 +755,8 @@ function VoidMiner(s: Stage) { | ||
| 755 | 755 | <line x1={f(drillX)} y1={f(drillY)} x2={f(cx + R * 1.0)} y2={f(cy - R * 0.2)} stroke={p.secondary} strokeWidth={2} strokeOpacity={0.9} strokeDasharray="6 4" /> |
| 756 | 756 | <Sym s={s} id="W" x={drillX} y={drillY} size={R * 0.56} rotate={30} /> |
| 757 | 757 | <Sym s={s} id="P1" x={cx - R * 0.72} y={cy - R * 0.5} size={R * 0.56} /> |
| 758 | − <Sym s={s} id="H1" x={cx - R * 0.55} y={cy + R * 0.82} size={R * 0.44} /> | |
| 759 | − <Sym s={s} id="H2" x={cx + R * 0.15} y={cy + R * 0.95} size={R * 0.4} /> | |
| 758 | + <Sym s={s} id="H1" x={cx - R * 0.55} y={cy + R * 0.8} size={R * 0.44} /> | |
| 759 | + <Sym s={s} id="H2" x={cx + R * 0.2} y={cy + R * 0.82} size={R * 0.4} /> | |
| 760 | 760 | <Small x={cx + R * 0.02} y={cy - R * 0.98} text="CORE LV 3 / 5" color={p.secondary} size={R * 0.075} tracking="0.2em" weight={700} /> |
| 761 | 761 | <Small x={cx + R * 1.12} y={cy + R * 0.4} text="×1 ×2 ×3 ×4 ×5" color={p.primary} size={R * 0.07} tracking="0.16em" rotate={-90} opacity={0.75} /> |
| 762 | 762 | </> |
@@ -796,7 +796,7 @@ function RoyalCircuit(s: Stage) { | ||
| 796 | 796 | <path d={track} stroke="#1d1d26" strokeWidth={f(R * 0.56)} fill="none" strokeLinecap="round" /> |
| 797 | 797 | <path d={track} stroke="#fff" strokeOpacity={0.35} strokeWidth={2} fill="none" strokeDasharray="14 18" /> |
| 798 | 798 | <path d={streaks} stroke={p.primary} strokeOpacity={0.55} strokeWidth={2} strokeLinecap="round" /> |
| 799 | − <rect x={f(cx - R * 1.3)} y={f(cy + R * 1.02)} width={f(R * 2.6)} height={f(R * 0.16)} fill={`url(#${id}-rc0)`} /> | |
| 799 | + <rect x={f(cx - R * 1.3)} y={f(cy + R * 0.9)} width={f(R * 2.6)} height={f(R * 0.14)} fill={`url(#${id}-rc0)`} /> | |
| 800 | 800 | <Sym s={s} id="W" x={cx + R * 0.1} y={cy + R * 0.08} size={R * 0.96} rotate={-10} /> |
| 801 | 801 | {!compact ? ( |
| 802 | 802 | <> |
@@ -831,7 +831,7 @@ function DragonCore(s: Stage) { | ||
| 831 | 831 | const scales = [ |
| 832 | 832 | { x: cx + R * 0.95, y: cy + R * 0.45, r: 15 }, |
| 833 | 833 | { x: cx + R * 0.72, y: cy + R * 0.8, r: 30 }, |
| 834 | − { x: cx + R * 0.35, y: cy + R * 1.0, r: 45 }, | |
| 834 | + { x: cx + R * 0.4, y: cy + R * 0.8, r: 45 }, | |
| 835 | 835 | ]; |
| 836 | 836 | return ( |
| 837 | 837 | <g> |
@@ -854,7 +854,7 @@ function DragonCore(s: Stage) { | ||
| 854 | 854 | <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.56} rotate={-12} /> |
| 855 | 855 | <Sym s={s} id="S" x={cx - R * 0.95} y={cy - R * 0.75} size={R * 0.42} /> |
| 856 | 856 | <Sym s={s} id="H1" x={cx + R * 0.95} y={cy - R * 0.75} size={R * 0.42} /> |
| 857 | − <Small x={cx} y={cy + R * 1.24} text="DRAGON CHARGE 13 / 20" color={p.secondary} size={R * 0.075} tracking="0.2em" weight={700} opacity={0.9} /> | |
| 857 | + <Small x={cx} y={cy + R * 1.0} text="DRAGON CHARGE 13 / 20" color={p.secondary} size={R * 0.075} tracking="0.2em" weight={700} opacity={0.9} /> | |
| 858 | 858 | </> |
| 859 | 859 | ) : null} |
| 860 | 860 | <path d={embers} fill={p.secondary} fillOpacity={0.75} /> |
@@ -869,7 +869,7 @@ function DeepTreasure(s: Stage) { | ||
| 869 | 869 | for (let i = 0; i < 26; i++) bubbles.push([s.rnd() * W, cy - R * 1.3 + s.rnd() * R * 2.6, 2 + s.rnd() * R * 0.04]); |
| 870 | 870 | const levels = ["×1", "×2", "×3", "×4", "×6", "×8", "×10"]; |
| 871 | 871 | const top = cy - R * 1.05; |
| 872 | − const lh = R * 0.36; | |
| 872 | + const lh = R * 0.3; | |
| 873 | 873 | const chest = (x: number, y: number, w: number, key: string) => ( |
| 874 | 874 | <g key={key}> |
| 875 | 875 | <rect x={f(x - w / 2)} y={f(y - w * 0.28)} width={f(w)} height={f(w * 0.62)} rx={f(w * 0.06)} fill="#7a4a12" stroke="#fbbf24" strokeWidth={1.5} /> |
@@ -896,7 +896,7 @@ function DeepTreasure(s: Stage) { | ||
| 896 | 896 | : null} |
| 897 | 897 | {!compact ? chest(cx - R * 0.78, cy - R * 0.5, R * 0.42, "c1") : null} |
| 898 | 898 | {!compact ? chest(cx - R * 0.3, cy + R * 0.15, R * 0.5, "c2") : null} |
| 899 | − {chest(cx + R * 0.3, cy + R * 0.85, R * 0.6, "c3")} | |
| 899 | + {chest(cx + R * 0.3, cy + R * 0.72, R * 0.6, "c3")} | |
| 900 | 900 | <Sym s={s} id="P1" x={cx + R * 0.3} y={cy + R * 0.2} size={R * 0.9} /> |
| 901 | 901 | {!compact ? ( |
| 902 | 902 | <> |
@@ -1007,7 +1007,7 @@ function WildTemple(s: Stage) { | ||
| 1007 | 1007 | <Sym s={s} id="H1" x={cx + R * 0.98} y={cy + R * 0.62} size={R * 0.5} /> |
| 1008 | 1008 | <Sym s={s} id="H2" x={cx - R * 1.05} y={cy - R * 0.45} size={R * 0.4} /> |
| 1009 | 1009 | <Sym s={s} id="H2" x={cx + R * 1.05} y={cy - R * 0.45} size={R * 0.4} /> |
| 1010 | − <Sym s={s} id="M1" x={cx + R * 0.5} y={cy + R * 1.0} size={R * 0.34} rotate={30} /> | |
| 1010 | + <Sym s={s} id="M1" x={cx + R * 0.55} y={cy + R * 0.85} size={R * 0.34} rotate={30} /> | |
| 1011 | 1011 | </> |
| 1012 | 1012 | ) : null} |
| 1013 | 1013 | <path d={motes} fill={p.secondary} fillOpacity={0.55} /> |
@@ -1057,7 +1057,7 @@ function ZeroGravity(s: Stage) { | ||
| 1057 | 1057 | <Sym s={s} id="P1" x={cx - R * 0.95} y={cy + R * 0.02} size={R * 0.48} /> |
| 1058 | 1058 | <Sym s={s} id="S" x={cx + R * 1.05} y={cy + R * 0.42} size={R * 0.42} /> |
| 1059 | 1059 | <Sym s={s} id="H2" x={cx + R * 0.02} y={cy - R * 1.12} size={R * 0.38} rotate={12} /> |
| 1060 | − <Sym s={s} id="M1" x={cx + R * 0.98} y={cy + R * 1.0} size={R * 0.34} rotate={-30} /> | |
| 1060 | + <Sym s={s} id="M1" x={cx + R * 1.0} y={cy + R * 0.85} size={R * 0.34} rotate={-30} /> | |
| 1061 | 1061 | </> |
| 1062 | 1062 | ) : null} |
| 1063 | 1063 | </g> |
@@ -1073,7 +1073,7 @@ function ReelReactor(s: Stage) { | ||
| 1073 | 1073 | const at = (t: number) => a0 + (a1 - a0) * t; |
| 1074 | 1074 | const needle = at(0.72); |
| 1075 | 1075 | let hazard = ""; |
| 1076 | − const hy = cy + R * 1.08; | |
| 1076 | + const hy = cy + R * 0.95; | |
| 1077 | 1077 | for (let x = -R * 0.3; x < W + R; x += R * 0.22) hazard += `M${f(x)} ${f(hy)}h${f(R * 0.11)}l${f(-R * 0.1)} ${f(R * 0.12)}h${f(-R * 0.11)}z `; |
| 1078 | 1078 | const tick = (t: number, label: string, color: string) => { |
| 1079 | 1079 | const a = at(t); |
@@ -1096,7 +1096,7 @@ function ReelReactor(s: Stage) { | ||
| 1096 | 1096 | {!compact ? ( |
| 1097 | 1097 | <> |
| 1098 | 1098 | <Small x={cx} y={cy + R * 0.62} text="HEAT 72" color={p.secondary} size={R * 0.12} weight={800} tracking="0.06em" /> |
| 1099 | − <Pill x={cx + R * 0.72} y={cy - R * 1.1} text="MELTDOWN" color="#ef4444" ink="#ff8a8a" bg="#2a0808" size={R * 0.07} /> | |
| 1099 | + <Pill x={cx + R * 0.1} y={cy - R * 1.12} text="MELTDOWN" color="#ef4444" ink="#ff8a8a" bg="#2a0808" size={R * 0.07} /> | |
| 1100 | 1100 | <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.62} size={R * 0.5} /> |
| 1101 | 1101 | <Sym s={s} id="S" x={cx + R * 0.9} y={cy + R * 0.62} size={R * 0.5} rotate={15} /> |
| 1102 | 1102 | <Sym s={s} id="H1" x={cx - R * 1.05} y={cy - R * 0.55} size={R * 0.4} /> |
@@ -1157,16 +1157,16 @@ function SpinzaOriginal(s: Stage) { | ||
| 1157 | 1157 | <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} plate={false} /> |
| 1158 | 1158 | {!compact ? ( |
| 1159 | 1159 | <> |
| 1160 | − <Small x={cx} y={cy + R * 0.98} text="MEGA METER 19 / 30" color={p.glow} size={R * 0.07} tracking="0.22em" weight={700} opacity={0.9} /> | |
| 1160 | + <Small x={cx} y={cy + R * 0.9} text="MEGA METER 19 / 30" color={p.glow} size={R * 0.07} tracking="0.22em" weight={700} opacity={0.9} /> | |
| 1161 | 1161 | <Sym s={s} id="MY" x={cx - R * 0.98} y={cy - R * 0.62} size={R * 0.62} /> |
| 1162 | 1162 | <Sym s={s} id="Z" x={cx - R * 0.98} y={cy + R * 0.3} size={R * 0.5} /> |
| 1163 | − <rect x={f(colX - R * 0.26)} y={f(cy - R * 1.25)} width={f(R * 0.52)} height={f(R * 2.4)} rx={f(R * 0.05)} fill="#fff" fillOpacity={0.04} stroke="#fff" strokeOpacity={0.12} /> | |
| 1163 | + <rect x={f(colX - R * 0.26)} y={f(cy - R * 1.12)} width={f(R * 0.52)} height={f(R * 2.12)} rx={f(R * 0.05)} fill="#fff" fillOpacity={0.04} stroke="#fff" strokeOpacity={0.12} /> | |
| 1164 | 1164 | {fall.map((idd, i) => ( |
| 1165 | 1165 | <Sym key={idd} s={s} id={idd} x={colX} y={cy - R * 0.95 + i * R * 0.56} size={R * 0.46} opacity={0.55 + i * 0.15} /> |
| 1166 | 1166 | ))} |
| 1167 | − <Pill x={cx - R * 0.5} y={cy - R * 1.18} text="WILD DROP" color={p.secondary} ink={p.secondary} size={R * 0.065} /> | |
| 1168 | − <Pill x={cx + R * 0.3} y={cy - R * 1.18} text="MULTIPLIER DROP" color={p.glow} ink={p.glow} size={R * 0.065} /> | |
| 1169 | − <Small x={cx} y={cy + R * 1.2} text="×1 ×2 ×3 ×4 ×6 ×8 ×10 ×15" color={p.primary} size={R * 0.07} tracking="0.16em" opacity={0.7} /> | |
| 1167 | + <Pill x={cx - R * 0.28} y={cy - R * 1.04} text="WILD DROP" color={p.secondary} ink={p.secondary} size={R * 0.065} /> | |
| 1168 | + <Pill x={cx + R * 0.44} y={cy - R * 1.04} text="MULTIPLIER DROP" color={p.glow} ink={p.glow} size={R * 0.065} /> | |
| 1169 | + <Small x={cx} y={cy + R * 1.02} text="×1 ×2 ×3 ×4 ×6 ×8 ×10 ×15" color={p.primary} size={R * 0.07} tracking="0.16em" opacity={0.7} /> | |
| 1170 | 1170 | </> |
| 1171 | 1171 | ) : null} |
| 1172 | 1172 | </g> |
@@ -1308,13 +1308,13 @@ function Title({ s, name, showName }: { s: Stage; name: string; showName: boolea | ||
| 1308 | 1308 | const eyebrow = spec.eyebrow && cfg.eye > 0 && v !== "tile"; |
| 1309 | 1309 | const eyeSize = v === "hero" ? size * 0.14 : v === "banner" ? size * 0.15 : Math.max(9.5, size * 0.2); |
| 1310 | 1310 | const fontStyle: React.CSSProperties = { fontFamily: FONT, letterSpacing: `${tracking}em` }; |
| 1311 | − const transform = spec.skew ? `skewX(${spec.skew})` : undefined; | |
| 1312 | 1311 | |
| 1313 | 1312 | const render = (props: React.SVGProps<SVGTextElement>, key: string) => |
| 1314 | 1313 | lines.map((l, i) => { |
| 1315 | 1314 | const isSub = spec.subline && i === 1; |
| 1316 | 1315 | const y = isSub ? baseY + subSize * 1.7 : baseY + i * lineH; |
| 1317 | 1316 | const base: React.CSSProperties = isSub ? { fontFamily: FONT, letterSpacing: "0.32em" } : fontStyle; |
| 1317 | + const transform = spec.skew ? `translate(${f(x)} ${f(y)}) skewX(${spec.skew}) translate(${f(-x)} ${f(-y)})` : undefined; | |
| 1318 | 1318 | return ( |
| 1319 | 1319 | <text key={`${key}${i}`} x={f(x)} y={f(y)} textAnchor={anchor} fontSize={f(isSub ? subSize : size)} fontWeight={isSub ? 700 : weight} transform={transform} {...props} style={{ ...base, ...props.style }}> |
| 1320 | 1320 | {l} |
added
games/certifications/bank-heist.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "bank-heist", | |
| 3 | + "name": "Bank Heist", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.96391712, | |
| 8 | + "deviation": 0.003917119999999996, | |
| 9 | + "hitRate": 0.222629, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 490.31, | |
| 13 | + "stdDev": 4.352455250737556, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 2500×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "0.392% (tolerance ±0.92%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "96.39%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "490.3× (cap 2500×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.538Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554742, | |
| 67 | + "share": 0.777371 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 135679, | |
| 74 | + "share": 0.0678395 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79436, | |
| 81 | + "share": 0.039718 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143327, | |
| 88 | + "share": 0.0716635 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47641, | |
| 95 | + "share": 0.0238205 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23971, | |
| 102 | + "share": 0.0119855 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14602, | |
| 109 | + "share": 0.007301 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 346, | |
| 116 | + "share": 0.000173 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 256, | |
| 123 | + "share": 0.000128 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.9390514 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9494347 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9586966 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.9608768 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.96147524 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9642069666666667 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9594297714285714 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.9628259 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9643958666666667 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.96498998 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9642288181818182 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.96441395 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9657298307692308 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9646824857142857 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9639896533333333 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.9642843625 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9641510117647059 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9650899444444444 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.965027852631579 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.96534183 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9647746857142857 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9648164636363636 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9642805043478261 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.963466475 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.963158392 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.963437423076923 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9637005703703704 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9648829785714286 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9652764551724138 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9650323133333333 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9638652967741935 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.96357528125 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9641130666666666 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9641277705882353 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9643895371428571 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9642674055555556 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9640625027027027 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9635775736842105 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9641630205128205 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.96391712 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Signal jammer": 600223 | |
| 297 | + }, | |
| 298 | + "durationMs": 134 | |
| 299 | +} | |
added
games/certifications/black-hole.json
+297 −0
@@ -0,0 +1,297 @@ | ||
| 1 | +{ | |
| 2 | + "game": "black-hole", | |
| 3 | + "name": "Black Hole", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.95368702, | |
| 8 | + "deviation": -0.0063129799999999126, | |
| 9 | + "hitRate": 0.2224205, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 489.78, | |
| 13 | + "stdDev": 4.167256251226399, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 10000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.631% (tolerance ±0.88%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.37%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "489.8× (cap 10000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.773Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1555159, | |
| 67 | + "share": 0.7775795 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 136143, | |
| 74 | + "share": 0.0680715 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79553, | |
| 81 | + "share": 0.0397765 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 142705, | |
| 88 | + "share": 0.0713525 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47848, | |
| 95 | + "share": 0.023924 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23848, | |
| 102 | + "share": 0.011924 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14190, | |
| 109 | + "share": 0.007095 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 312, | |
| 116 | + "share": 0.000156 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 242, | |
| 123 | + "share": 0.000121 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.921937 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9377734 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9353091333333333 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.93739225 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.93914564 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9347733 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9388381714285714 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.943534225 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9437072 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.94537304 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9467291818181818 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9489026 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9520744923076923 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9523814285714286 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9508651866666666 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.9510268375 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9504187294117648 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9504978555555555 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9502219052631579 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95194051 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9534165714285714 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9541829 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9539388086956522 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9536442166666667 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.953784856 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9539154615384615 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9542466888888889 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9538773285714286 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9536818620689655 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9545845466666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9545137741935484 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.95469646875 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9549692848484849 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9543618882352941 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9543698685714286 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9543075722222222 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9546324378378378 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9546098052631579 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9546309282051282 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.95368702 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": {}, | |
| 296 | + "durationMs": 104 | |
| 297 | +} | |
added
games/certifications/core-meltdown.json
+300 −0
@@ -0,0 +1,300 @@ | ||
| 1 | +{ | |
| 2 | + "game": "core-meltdown", | |
| 3 | + "name": "Core Meltdown", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.954057215, | |
| 8 | + "deviation": -0.005942785000000006, | |
| 9 | + "hitRate": 0.223306, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 494.09, | |
| 13 | + "stdDev": 4.097552547967906, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.594% (tolerance ±0.87%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.41%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "494.1× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:40.153Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1553388, | |
| 67 | + "share": 0.776694 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 136851, | |
| 74 | + "share": 0.0684255 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79895, | |
| 81 | + "share": 0.0399475 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143513, | |
| 88 | + "share": 0.0717565 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47803, | |
| 95 | + "share": 0.0239015 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23696, | |
| 102 | + "share": 0.011848 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14316, | |
| 109 | + "share": 0.007158 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 331, | |
| 116 | + "share": 0.0001655 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 207, | |
| 123 | + "share": 0.0001035 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.947085 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9343246 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9442656666666667 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.9465809 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.94889528 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9459146 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9462324 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.9444719 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9446777111111111 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.94909574 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9501834909090909 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9498570166666667 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9506063692307692 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9512420714285714 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9517908133333334 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.9524256 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9533394588235294 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9521214666666666 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9524753789473684 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95258458 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9535204666666667 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9536495181818182 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.953184947826087 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9544278333333334 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.954959856 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.954769376923077 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9548739037037037 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9549191928571429 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9545073793103448 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9543815066666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9548629483870967 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.95486700625 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9546481818181818 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9551030529411765 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9554977371428571 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9563426055555555 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9555952540540541 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9547046315789474 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9543590512820512 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.954057215 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Power spike": 337235, | |
| 297 | + "Coolant surge": 1011023 | |
| 298 | + }, | |
| 299 | + "durationMs": 236 | |
| 300 | +} | |
added
games/certifications/deep-dive.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "deep-dive", | |
| 3 | + "name": "Deep Dive", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.960874195, | |
| 8 | + "deviation": 0.0008741950000000775, | |
| 9 | + "hitRate": 0.2230625, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 496.58, | |
| 13 | + "stdDev": 4.223505991227474, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "0.087% (tolerance ±0.90%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "96.09%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "496.6× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.274Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1553875, | |
| 67 | + "share": 0.7769375 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 136388, | |
| 74 | + "share": 0.068194 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79522, | |
| 81 | + "share": 0.039761 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143328, | |
| 88 | + "share": 0.071664 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47784, | |
| 95 | + "share": 0.023892 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 24199, | |
| 102 | + "share": 0.0120995 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14322, | |
| 109 | + "share": 0.007161 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 327, | |
| 116 | + "share": 0.0001635 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 255, | |
| 123 | + "share": 0.0001275 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.981744 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9688415 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9660274666666666 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.95946645 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.95799912 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9605313 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9544169142857143 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.956806225 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9591186222222222 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.9582865 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9583992545454545 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9582501166666667 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9575540307692307 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9579928571428571 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9563829866666667 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.956648 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9576155058823529 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9573925555555556 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9598498210526316 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95725369 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.956701380952381 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9588518727272727 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9587854173913043 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9592476833333333 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.959404616 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9605161615384615 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9614446666666666 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9608630642857143 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9613541931034483 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9613371266666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9614718967741935 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.9618734875 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9609947878787879 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.961304194117647 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9610557142857142 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9604515166666666 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.960938627027027 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9602719631578948 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9605036974358975 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.960874195 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Leviathan current": 900870 | |
| 297 | + }, | |
| 298 | + "durationMs": 180 | |
| 299 | +} | |
added
games/certifications/elevator-999.json
+297 −0
@@ -0,0 +1,297 @@ | ||
| 1 | +{ | |
| 2 | + "game": "elevator-999", | |
| 3 | + "name": "Elevator 999", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.95612571, | |
| 8 | + "deviation": -0.003874289999999947, | |
| 9 | + "hitRate": 0.222886, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 493.46, | |
| 13 | + "stdDev": 4.1735026631809315, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 10000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.387% (tolerance ±0.89%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.61%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "493.5× (cap 10000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:40.408Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554228, | |
| 67 | + "share": 0.777114 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 135939, | |
| 74 | + "share": 0.0679695 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79524, | |
| 81 | + "share": 0.039762 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143668, | |
| 88 | + "share": 0.071834 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47831, | |
| 95 | + "share": 0.0239155 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 24002, | |
| 102 | + "share": 0.012001 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14244, | |
| 109 | + "share": 0.007122 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 318, | |
| 116 | + "share": 0.000159 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 246, | |
| 123 | + "share": 0.000123 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.9487956 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.946483 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9573534666666667 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.9524104 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.94865168 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9495020666666667 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9475804857142858 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.947544775 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9488880222222222 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.94755926 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9461799636363636 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9465434 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9502770923076923 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9491107857142858 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9505048666666667 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.95172005 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9514129764705882 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9521773444444445 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9526824210526316 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95212469 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9524791619047619 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9540800727272727 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9545876347826087 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9541590833333333 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.954415472 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9558631923076923 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9565765111111111 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9553519714285714 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9552527172413793 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9553981466666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9556198387096774 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.955715275 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9556010484848485 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9555573294117647 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9559632114285714 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9563817555555556 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9553239405405406 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9555535315789474 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9557663487179487 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.95612571 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": {}, | |
| 296 | + "durationMs": 102 | |
| 297 | +} | |
added
games/certifications/freefall.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "freefall", | |
| 3 | + "name": "Freefall", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.95592331, | |
| 8 | + "deviation": -0.004076689999999994, | |
| 9 | + "hitRate": 0.2225345, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 488.05, | |
| 13 | + "stdDev": 4.222390940107298, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.408% (tolerance ±0.90%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.59%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "488.1× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.917Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554931, | |
| 67 | + "share": 0.7774655 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 135705, | |
| 74 | + "share": 0.0678525 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79838, | |
| 81 | + "share": 0.039919 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143175, | |
| 88 | + "share": 0.0715875 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47642, | |
| 95 | + "share": 0.023821 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23893, | |
| 102 | + "share": 0.0119465 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14238, | |
| 109 | + "share": 0.007119 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 336, | |
| 116 | + "share": 0.000168 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 242, | |
| 123 | + "share": 0.000121 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.9455122 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.95023 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9491773333333333 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.95242235 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.95341364 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9565252 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9588479428571428 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.9583923 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.959085 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.95956182 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9600586909090909 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9625846 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9619937538461538 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9598496285714285 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9605832933333334 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.9599182875 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9606216235294117 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9605883222222222 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.960027652631579 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95981255 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9599223809523809 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.959446 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9593016608695653 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9579431333333334 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.95793356 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9572521 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9586696222222222 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9580878357142857 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9575984413793104 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.95777812 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9592348 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.95834230625 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9585262181818182 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9580600882352941 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9583517771428571 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9580838333333334 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9578505567567568 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9569594684210526 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9566630974358974 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.95592331 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Thermal": 500918 | |
| 297 | + }, | |
| 298 | + "durationMs": 144 | |
| 299 | +} | |
added
games/certifications/rocket-run.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "rocket-run", | |
| 3 | + "name": "Rocket Run", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.95872351, | |
| 8 | + "deviation": -0.0012764899999999635, | |
| 9 | + "hitRate": 0.222655, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 496.19, | |
| 13 | + "stdDev": 4.190558032312003, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 10000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.128% (tolerance ±0.89%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.87%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "496.2× (cap 10000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.404Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554690, | |
| 67 | + "share": 0.777345 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 135789, | |
| 74 | + "share": 0.0678945 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79462, | |
| 81 | + "share": 0.039731 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143319, | |
| 88 | + "share": 0.0716595 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47888, | |
| 95 | + "share": 0.023944 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23883, | |
| 102 | + "share": 0.0119415 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14390, | |
| 109 | + "share": 0.007195 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 318, | |
| 116 | + "share": 0.000159 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 261, | |
| 123 | + "share": 0.0001305 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.941627 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9437129 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.957419 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.94758415 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.94541232 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9559577333333333 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9613262 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.959756825 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9600729333333333 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.95988924 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9573219272727272 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9563683666666667 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9558367538461539 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9554104714285714 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9554266133333333 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.955499925 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9568464 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9556221 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9550166631578947 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95432584 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9553890571428572 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9550377363636363 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9555198869565218 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9558961083333334 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.955973944 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9560392153846153 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9562569111111111 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9578046857142857 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9579996965517241 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9577707533333333 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9565746064516129 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.9574897375 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9569376181818182 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9573828647058824 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9578783085714285 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9587225388888889 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.958616572972973 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9588811789473685 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9588245384615385 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.95872351 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Afterburner": 499587 | |
| 297 | + }, | |
| 298 | + "durationMs": 129 | |
| 299 | +} | |
added
games/certifications/skyfall.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "skyfall", | |
| 3 | + "name": "Skyfall", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.95860249, | |
| 8 | + "deviation": -0.0013975099999999907, | |
| 9 | + "hitRate": 0.222615, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 483.01, | |
| 13 | + "stdDev": 4.239707875888016, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.140% (tolerance ±0.90%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.86%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "483.0× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:39.093Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554770, | |
| 67 | + "share": 0.777385 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 136034, | |
| 74 | + "share": 0.068017 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79026, | |
| 81 | + "share": 0.039513 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 144004, | |
| 88 | + "share": 0.072002 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47484, | |
| 95 | + "share": 0.023742 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23707, | |
| 102 | + "share": 0.0118535 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14398, | |
| 109 | + "share": 0.007199 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 317, | |
| 116 | + "share": 0.0001585 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 260, | |
| 123 | + "share": 0.00013 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.9434694 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9585828 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9522996 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.9610722 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.96353284 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9633070333333333 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9618062571428572 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.9597582 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9606117777777777 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.96003238 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9617228545454546 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9624842333333333 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9603122 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9613523 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9590834133333334 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.9580979 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9583622941176471 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9577526777777777 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9572920421052632 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95663994 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.957581 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9582303636363636 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9585211739130435 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.958033575 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.958688112 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9574196076923077 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9588178296296296 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9584433714285714 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.959079075862069 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9580548933333334 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9580809225806451 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.9587533 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9587091454545454 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9581568764705882 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9577804971428572 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9574161777777778 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9587515081081082 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9594349315789473 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9593111641025641 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.95860249 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Tailwind": 698209 | |
| 297 | + }, | |
| 298 | + "durationMs": 144 | |
| 299 | +} | |
added
games/certifications/storm-chase.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "storm-chase", | |
| 3 | + "name": "Storm Chase", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 2000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.96051224, | |
| 8 | + "deviation": 0.0005122400000000527, | |
| 9 | + "hitRate": 0.2228855, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 496.84, | |
| 13 | + "stdDev": 4.3140528179673545, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "2,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "0.051% (tolerance ±0.92%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "96.05%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "496.8× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:54:40.304Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 1554229, | |
| 67 | + "share": 0.7771145 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 136259, | |
| 74 | + "share": 0.0681295 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 79466, | |
| 81 | + "share": 0.039733 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 143458, | |
| 88 | + "share": 0.071729 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 47725, | |
| 95 | + "share": 0.0238625 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 23938, | |
| 102 | + "share": 0.011969 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 14360, | |
| 109 | + "share": 0.00718 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 288, | |
| 116 | + "share": 0.000144 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 277, | |
| 123 | + "share": 0.0001385 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 50000, | |
| 136 | + "rtp": 0.9730172 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 100000, | |
| 140 | + "rtp": 0.9643629 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 150000, | |
| 144 | + "rtp": 0.9585766 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 200000, | |
| 148 | + "rtp": 0.95669855 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 250000, | |
| 152 | + "rtp": 0.95881768 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 300000, | |
| 156 | + "rtp": 0.9565838 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 350000, | |
| 160 | + "rtp": 0.9590141142857143 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 400000, | |
| 164 | + "rtp": 0.95782775 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 450000, | |
| 168 | + "rtp": 0.9578645777777778 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 500000, | |
| 172 | + "rtp": 0.95912488 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 550000, | |
| 176 | + "rtp": 0.9574977636363636 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 600000, | |
| 180 | + "rtp": 0.9575701666666666 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 650000, | |
| 184 | + "rtp": 0.9580566923076923 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 700000, | |
| 188 | + "rtp": 0.9587008571428571 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 750000, | |
| 192 | + "rtp": 0.9566733866666667 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 800000, | |
| 196 | + "rtp": 0.958426275 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 850000, | |
| 200 | + "rtp": 0.9592964705882353 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 900000, | |
| 204 | + "rtp": 0.9597390222222222 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 950000, | |
| 208 | + "rtp": 0.9582122947368421 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 1000000, | |
| 212 | + "rtp": 0.95642522 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 1050000, | |
| 216 | + "rtp": 0.9571531238095238 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 1100000, | |
| 220 | + "rtp": 0.9571702545454546 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 1150000, | |
| 224 | + "rtp": 0.9580738608695653 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 1200000, | |
| 228 | + "rtp": 0.9598063 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 1250000, | |
| 232 | + "rtp": 0.961180552 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 1300000, | |
| 236 | + "rtp": 0.9620072461538461 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 1350000, | |
| 240 | + "rtp": 0.9613264222222222 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 1400000, | |
| 244 | + "rtp": 0.9610075642857143 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 1450000, | |
| 248 | + "rtp": 0.9613439379310345 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 1500000, | |
| 252 | + "rtp": 0.9618556466666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 1550000, | |
| 256 | + "rtp": 0.9620184064516129 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 1600000, | |
| 260 | + "rtp": 0.961062275 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 1650000, | |
| 264 | + "rtp": 0.9602330909090909 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 1700000, | |
| 268 | + "rtp": 0.9603225823529412 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 1750000, | |
| 272 | + "rtp": 0.9609292628571429 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 1800000, | |
| 276 | + "rtp": 0.9610316055555556 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 1850000, | |
| 280 | + "rtp": 0.9605304486486487 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 1900000, | |
| 284 | + "rtp": 0.9598998894736842 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 1950000, | |
| 288 | + "rtp": 0.9598532871794871 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 2000000, | |
| 292 | + "rtp": 0.96051224 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Rear-flank gust": 599214 | |
| 297 | + }, | |
| 298 | + "durationMs": 150 | |
| 299 | +} | |
added
games/certifications/volcano.json
+299 −0
@@ -0,0 +1,299 @@ | ||
| 1 | +{ | |
| 2 | + "game": "volcano", | |
| 3 | + "name": "Volcano", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "spins": 10000000, | |
| 6 | + "configuredRtp": 0.96, | |
| 7 | + "observedRtp": 0.959338234, | |
| 8 | + "deviation": -0.0006617660000000081, | |
| 9 | + "hitRate": 0.2229919, | |
| 10 | + "bonusRate": 0, | |
| 11 | + "freeSpinRate": 0, | |
| 12 | + "maxWinMultiplier": 499.53, | |
| 13 | + "stdDev": 4.216198570180634, | |
| 14 | + "status": "PASS", | |
| 15 | + "checks": [ | |
| 16 | + { | |
| 17 | + "name": "definition", | |
| 18 | + "pass": true, | |
| 19 | + "detail": "rtp 0.96, cap 5000×" | |
| 20 | + }, | |
| 21 | + { | |
| 22 | + "name": "spins", | |
| 23 | + "pass": true, | |
| 24 | + "detail": "10,000,000 rounds" | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + "name": "rtp-deviation", | |
| 28 | + "pass": true, | |
| 29 | + "detail": "-0.066% (tolerance ±0.40%)" | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + "name": "rtp-band", | |
| 33 | + "pass": true, | |
| 34 | + "detail": "95.93%" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "max-win", | |
| 38 | + "pass": true, | |
| 39 | + "detail": "499.5× (cap 5000×)" | |
| 40 | + }, | |
| 41 | + { | |
| 42 | + "name": "commitment", | |
| 43 | + "pass": true, | |
| 44 | + "detail": "SHA-256 commit/reveal verified" | |
| 45 | + } | |
| 46 | + ], | |
| 47 | + "certifiedAt": "2026-09-08T02:55:00.512Z", | |
| 48 | + "rules": { | |
| 49 | + "maxDeviation": 0.004, | |
| 50 | + "minSpins": 1000000, | |
| 51 | + "rtpBand": [ | |
| 52 | + 0.93, | |
| 53 | + 0.99 | |
| 54 | + ], | |
| 55 | + "hitRateBand": [ | |
| 56 | + 0.08, | |
| 57 | + 0.6 | |
| 58 | + ], | |
| 59 | + "maxCappedShare": 0.0005 | |
| 60 | + }, | |
| 61 | + "distribution": [ | |
| 62 | + { | |
| 63 | + "label": "0×", | |
| 64 | + "min": 0, | |
| 65 | + "max": 0, | |
| 66 | + "count": 7770081, | |
| 67 | + "share": 0.7770081 | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "label": "1–1.5×", | |
| 71 | + "min": 1, | |
| 72 | + "max": 1.5, | |
| 73 | + "count": 680310, | |
| 74 | + "share": 0.068031 | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + "label": "1.5–2×", | |
| 78 | + "min": 1.5, | |
| 79 | + "max": 2, | |
| 80 | + "count": 398150, | |
| 81 | + "share": 0.039815 | |
| 82 | + }, | |
| 83 | + { | |
| 84 | + "label": "2–5×", | |
| 85 | + "min": 2, | |
| 86 | + "max": 5, | |
| 87 | + "count": 718172, | |
| 88 | + "share": 0.0718172 | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "label": "5–10×", | |
| 92 | + "min": 5, | |
| 93 | + "max": 10, | |
| 94 | + "count": 238792, | |
| 95 | + "share": 0.0238792 | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + "label": "10–20×", | |
| 99 | + "min": 10, | |
| 100 | + "max": 20, | |
| 101 | + "count": 119631, | |
| 102 | + "share": 0.0119631 | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "label": "20–50×", | |
| 106 | + "min": 20, | |
| 107 | + "max": 50, | |
| 108 | + "count": 72023, | |
| 109 | + "share": 0.0072023 | |
| 110 | + }, | |
| 111 | + { | |
| 112 | + "label": "50–100×", | |
| 113 | + "min": 50, | |
| 114 | + "max": 100, | |
| 115 | + "count": 1605, | |
| 116 | + "share": 0.0001605 | |
| 117 | + }, | |
| 118 | + { | |
| 119 | + "label": "100–500×", | |
| 120 | + "min": 100, | |
| 121 | + "max": 500, | |
| 122 | + "count": 1236, | |
| 123 | + "share": 0.0001236 | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + "label": "500×+", | |
| 127 | + "min": 500, | |
| 128 | + "max": null, | |
| 129 | + "count": 0, | |
| 130 | + "share": 0 | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "convergence": [ | |
| 134 | + { | |
| 135 | + "spins": 250000, | |
| 136 | + "rtp": 0.94169216 | |
| 137 | + }, | |
| 138 | + { | |
| 139 | + "spins": 500000, | |
| 140 | + "rtp": 0.94914986 | |
| 141 | + }, | |
| 142 | + { | |
| 143 | + "spins": 750000, | |
| 144 | + "rtp": 0.9507861866666667 | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "spins": 1000000, | |
| 148 | + "rtp": 0.95573486 | |
| 149 | + }, | |
| 150 | + { | |
| 151 | + "spins": 1250000, | |
| 152 | + "rtp": 0.956596928 | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + "spins": 1500000, | |
| 156 | + "rtp": 0.9575963 | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "spins": 1750000, | |
| 160 | + "rtp": 0.9575368114285714 | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + "spins": 2000000, | |
| 164 | + "rtp": 0.957738045 | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "spins": 2250000, | |
| 168 | + "rtp": 0.95767596 | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "spins": 2500000, | |
| 172 | + "rtp": 0.956817976 | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "spins": 2750000, | |
| 176 | + "rtp": 0.9571074036363636 | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "spins": 3000000, | |
| 180 | + "rtp": 0.9580076266666666 | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "spins": 3250000, | |
| 184 | + "rtp": 0.9587537661538461 | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + "spins": 3500000, | |
| 188 | + "rtp": 0.9579410371428572 | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "spins": 3750000, | |
| 192 | + "rtp": 0.9579896106666667 | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "spins": 4000000, | |
| 196 | + "rtp": 0.958503635 | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "spins": 4250000, | |
| 200 | + "rtp": 0.9576985623529412 | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "spins": 4500000, | |
| 204 | + "rtp": 0.9581201422222222 | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "spins": 4750000, | |
| 208 | + "rtp": 0.9584606673684211 | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + "spins": 5000000, | |
| 212 | + "rtp": 0.958283548 | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "spins": 5250000, | |
| 216 | + "rtp": 0.9588206609523809 | |
| 217 | + }, | |
| 218 | + { | |
| 219 | + "spins": 5500000, | |
| 220 | + "rtp": 0.95855014 | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + "spins": 5750000, | |
| 224 | + "rtp": 0.958530735652174 | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "spins": 6000000, | |
| 228 | + "rtp": 0.9584187233333333 | |
| 229 | + }, | |
| 230 | + { | |
| 231 | + "spins": 6250000, | |
| 232 | + "rtp": 0.9581929376 | |
| 233 | + }, | |
| 234 | + { | |
| 235 | + "spins": 6500000, | |
| 236 | + "rtp": 0.9583126523076924 | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "spins": 6750000, | |
| 240 | + "rtp": 0.9583265866666667 | |
| 241 | + }, | |
| 242 | + { | |
| 243 | + "spins": 7000000, | |
| 244 | + "rtp": 0.9591130057142857 | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "spins": 7250000, | |
| 248 | + "rtp": 0.9592312579310345 | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "spins": 7500000, | |
| 252 | + "rtp": 0.9592946186666667 | |
| 253 | + }, | |
| 254 | + { | |
| 255 | + "spins": 7750000, | |
| 256 | + "rtp": 0.9590616141935484 | |
| 257 | + }, | |
| 258 | + { | |
| 259 | + "spins": 8000000, | |
| 260 | + "rtp": 0.95862608375 | |
| 261 | + }, | |
| 262 | + { | |
| 263 | + "spins": 8250000, | |
| 264 | + "rtp": 0.9591213866666667 | |
| 265 | + }, | |
| 266 | + { | |
| 267 | + "spins": 8500000, | |
| 268 | + "rtp": 0.9591908482352941 | |
| 269 | + }, | |
| 270 | + { | |
| 271 | + "spins": 8750000, | |
| 272 | + "rtp": 0.9592896285714285 | |
| 273 | + }, | |
| 274 | + { | |
| 275 | + "spins": 9000000, | |
| 276 | + "rtp": 0.9596593677777778 | |
| 277 | + }, | |
| 278 | + { | |
| 279 | + "spins": 9250000, | |
| 280 | + "rtp": 0.9594241383783784 | |
| 281 | + }, | |
| 282 | + { | |
| 283 | + "spins": 9500000, | |
| 284 | + "rtp": 0.9593100105263158 | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + "spins": 9750000, | |
| 288 | + "rtp": 0.9592953548717948 | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "spins": 10000000, | |
| 292 | + "rtp": 0.959338234 | |
| 293 | + } | |
| 294 | + ], | |
| 295 | + "featureCounts": { | |
| 296 | + "Crystal vein": 2999811 | |
| 297 | + }, | |
| 298 | + "durationMs": 619 | |
| 299 | +} | |
modified
games/src/client.ts
+1 −0
@@ -4,3 +4,4 @@ | ||
| 4 | 4 | * the RNG and engine never ship to the client. |
| 5 | 5 | */ |
| 6 | 6 | export { GAMES, GAMES_BY_SLUG, getGame, FEATURED_ORDER, FIRST_GAME_RECOMMENDATIONS } from "./index"; |
| 7 | +export { CRASH_GAMES, CRASH_BY_SLUG } from "./crash"; | |
added
games/src/crash/index.ts
+238 −0
@@ -0,0 +1,238 @@ | ||
| 1 | +import { defineCrashGame, type CrashGameDefinition } from "@spinza/game-core"; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Spinza Risk Games — ten cash-out games sharing one provably-fair engine. | |
| 5 | + * Every game: P(crash ≥ x) = 0.96 / x. Curves and events only change pacing. | |
| 6 | + */ | |
| 7 | + | |
| 8 | +const RULES_COMMON = [ | |
| 9 | + "Place a bet and start the round. The multiplier begins at 1.00× and climbs.", | |
| 10 | + "Press the big button at any moment to lock in your bet × the current multiplier.", | |
| 11 | + "If the round ends before you cash out, the bet is lost.", | |
| 12 | + "Optional auto cash-out settles automatically at your target multiplier.", | |
| 13 | + "Every round is provably fair: the crash point is committed (SHA-256) before the round starts and revealed after it ends.", | |
| 14 | +]; | |
| 15 | + | |
| 16 | +export const skyfall = defineCrashGame({ | |
| 17 | + slug: "skyfall", | |
| 18 | + name: "Skyfall", | |
| 19 | + version: "1.0.0", | |
| 20 | + tagline: "How high before the sky runs out?", | |
| 21 | + description: "A jet climbs through clouds, storms and the stratosphere into open space. The multiplier rises with the altitude — cash out before the aircraft vanishes.", | |
| 22 | + theme: "aviation ascent", | |
| 23 | + rtp: 0.96, | |
| 24 | + maxMultiplier: 5000, | |
| 25 | + volatility: "high", | |
| 26 | + curve: { type: "exp", k: 0.075 }, | |
| 27 | + events: { chance: 0.35, max: 1, window: [3, 18], kinds: [{ id: "tailwind", label: "Tailwind", factor: 1.8, duration: [2, 4], weight: 1 }] }, | |
| 28 | + milestones: [ | |
| 29 | + { multiplier: 1.5, label: "Cloud layer" }, | |
| 30 | + { multiplier: 3, label: "Storm front" }, | |
| 31 | + { multiplier: 8, label: "Stratosphere" }, | |
| 32 | + { multiplier: 25, label: "Edge of space" }, | |
| 33 | + { multiplier: 100, label: "Orbit" }, | |
| 34 | + ], | |
| 35 | + presentation: { scene: "sky", palette: { primary: "#7dd3fc", secondary: "#fbbf24", glow: "#bae6fd", bg: "#08111f", surface: "#0f2540" }, verb: "CASH OUT", unit: "m", unitScale: 1200, ambience: "lunar" }, | |
| 36 | + rules: [...RULES_COMMON, "A Tailwind event speeds the climb for a few seconds without changing the odds."], | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export const deepDive = defineCrashGame({ | |
| 40 | + slug: "deep-dive", | |
| 41 | + name: "Deep Dive", | |
| 42 | + version: "1.0.0", | |
| 43 | + tagline: "The abyss pays. The abyss also implodes.", | |
| 44 | + description: "A research submarine sinks into the ocean. 1,000 m is ×2, 3,000 m is ×5, the abyss is ×20 and beyond. Surface before the hull gives in. Leviathans sometimes drag you deeper, faster.", | |
| 45 | + theme: "deep-sea descent", | |
| 46 | + rtp: 0.96, | |
| 47 | + maxMultiplier: 5000, | |
| 48 | + volatility: "high", | |
| 49 | + curve: { type: "exp", k: 0.07 }, | |
| 50 | + events: { chance: 0.3, max: 2, window: [4, 20], kinds: [{ id: "leviathan", label: "Leviathan current", factor: 2.2, duration: [1.5, 3], weight: 1 }] }, | |
| 51 | + milestones: [ | |
| 52 | + { multiplier: 2, label: "1,000 m — twilight zone" }, | |
| 53 | + { multiplier: 5, label: "3,000 m — midnight zone" }, | |
| 54 | + { multiplier: 20, label: "Abyssal plain" }, | |
| 55 | + { multiplier: 80, label: "Hadal trench" }, | |
| 56 | + ], | |
| 57 | + presentation: { scene: "ocean", palette: { primary: "#22d3ee", secondary: "#34d399", glow: "#67e8f9", bg: "#03111c", surface: "#062a3a" }, verb: "SURFACE", unit: "m", unitScale: 500, ambience: "ocean" }, | |
| 58 | + rules: [...RULES_COMMON, "Leviathan currents briefly accelerate the descent — a pacing event only."], | |
| 59 | +}); | |
| 60 | + | |
| 61 | +export const rocketRun = defineCrashGame({ | |
| 62 | + slug: "rocket-run", | |
| 63 | + name: "Rocket Run", | |
| 64 | + version: "1.0.0", | |
| 65 | + tagline: "Mars or bust.", | |
| 66 | + description: "A rocket leaves the pad for Mars. Every second adds to the multiplier and some runs reach absurd heights — until an engine failure, a collision or an overheat ends it instantly. Secure the payload whenever you like.", | |
| 67 | + theme: "space launch", | |
| 68 | + rtp: 0.96, | |
| 69 | + maxMultiplier: 10000, | |
| 70 | + volatility: "extreme", | |
| 71 | + curve: { type: "exp", k: 0.095 }, | |
| 72 | + events: { chance: 0.25, max: 1, window: [2, 14], kinds: [{ id: "afterburner", label: "Afterburner", factor: 2, duration: [1.5, 3], weight: 1 }] }, | |
| 73 | + milestones: [ | |
| 74 | + { multiplier: 2, label: "Max-Q" }, | |
| 75 | + { multiplier: 5, label: "Stage separation" }, | |
| 76 | + { multiplier: 15, label: "Escape velocity" }, | |
| 77 | + { multiplier: 60, label: "Trans-Mars injection" }, | |
| 78 | + { multiplier: 300, label: "Mars orbit" }, | |
| 79 | + ], | |
| 80 | + presentation: { scene: "rocket", palette: { primary: "#fb923c", secondary: "#f472b6", glow: "#fdba74", bg: "#0a0612", surface: "#1b0f2a" }, verb: "SECURE PAYLOAD", unit: "km", unitScale: 40, ambience: "space" }, | |
| 81 | + rules: [...RULES_COMMON, "Afterburner windows speed up the climb for a moment."], | |
| 82 | +}); | |
| 83 | + | |
| 84 | +export const bankHeist = defineCrashGame({ | |
| 85 | + slug: "bank-heist", | |
| 86 | + name: "Bank Heist", | |
| 87 | + version: "1.0.0", | |
| 88 | + tagline: "The bag keeps filling. The sirens keep coming.", | |
| 89 | + description: "You are inside the vault and the bag of fictional cash keeps growing: ×1.1, ×1.4, ×2, ×3… Stay longer for more, but the police can arrive at any second. Escape before they do and the multiplier is yours.", | |
| 90 | + theme: "heist", | |
| 91 | + rtp: 0.96, | |
| 92 | + maxMultiplier: 2500, | |
| 93 | + volatility: "medium", | |
| 94 | + curve: { type: "exp", k: 0.062 }, | |
| 95 | + events: { chance: 0.3, max: 1, window: [3, 16], kinds: [{ id: "jammer", label: "Signal jammer", factor: 1.6, duration: [2, 4], weight: 1 }] }, | |
| 96 | + milestones: [ | |
| 97 | + { multiplier: 1.4, label: "Teller drawers" }, | |
| 98 | + { multiplier: 2, label: "Safe deposit boxes" }, | |
| 99 | + { multiplier: 4, label: "Main vault" }, | |
| 100 | + { multiplier: 12, label: "Diamond room" }, | |
| 101 | + { multiplier: 50, label: "Federal reserve" }, | |
| 102 | + ], | |
| 103 | + presentation: { scene: "bank", palette: { primary: "#c9a961", secondary: "#ef4444", glow: "#e8cf8f", bg: "#0b0a08", surface: "#1b1811" }, verb: "ESCAPE NOW", unit: "bags", unitScale: 1, ambience: "heist" }, | |
| 104 | + rules: [...RULES_COMMON, "Signal jammer windows fill the bag faster for a few seconds."], | |
| 105 | +}); | |
| 106 | + | |
| 107 | +export const volcano = defineCrashGame({ | |
| 108 | + slug: "volcano", | |
| 109 | + name: "Volcano", | |
| 110 | + version: "1.0.0", | |
| 111 | + tagline: "Deeper crystals. Rising lava.", | |
| 112 | + description: "An explorer descends into a live volcano to harvest crystals. The deeper, the richer — but the lava level rises unpredictably. Trigger EVACUATE before the eruption.", | |
| 113 | + theme: "volcanic descent", | |
| 114 | + rtp: 0.96, | |
| 115 | + maxMultiplier: 5000, | |
| 116 | + volatility: "high", | |
| 117 | + curve: { type: "exp", k: 0.08 }, | |
| 118 | + events: { chance: 0.3, max: 1, window: [3, 15], kinds: [{ id: "vein", label: "Crystal vein", factor: 1.9, duration: [1.5, 3], weight: 1 }] }, | |
| 119 | + milestones: [ | |
| 120 | + { multiplier: 1.5, label: "Ash caves" }, | |
| 121 | + { multiplier: 3, label: "Obsidian galleries" }, | |
| 122 | + { multiplier: 8, label: "Crystal chamber" }, | |
| 123 | + { multiplier: 30, label: "Magma core" }, | |
| 124 | + ], | |
| 125 | + presentation: { scene: "volcano", palette: { primary: "#ff5c3d", secondary: "#fbbf24", glow: "#ff8a5c", bg: "#120604", surface: "#2a0c07" }, verb: "EVACUATE", unit: "m", unitScale: 60, ambience: "fire" }, | |
| 126 | + rules: [...RULES_COMMON, "Crystal veins speed up the harvest briefly."], | |
| 127 | +}); | |
| 128 | + | |
| 129 | +export const blackHole = defineCrashGame({ | |
| 130 | + slug: "black-hole", | |
| 131 | + name: "Black Hole", | |
| 132 | + version: "1.0.0", | |
| 133 | + tagline: "Time slows. The multiplier doesn't.", | |
| 134 | + description: "Your ship drifts toward an event horizon. The multiplier creeps at first, then explodes as space-time bends: ×10 becomes ×15, ×25, ×50 in a heartbeat. WARP OUT before you are pulled in.", | |
| 135 | + theme: "relativistic descent", | |
| 136 | + rtp: 0.96, | |
| 137 | + maxMultiplier: 10000, | |
| 138 | + volatility: "extreme", | |
| 139 | + curve: { type: "power", a: 0.02, p: 2.6 }, | |
| 140 | + milestones: [ | |
| 141 | + { multiplier: 1.5, label: "Accretion disk" }, | |
| 142 | + { multiplier: 4, label: "Photon sphere" }, | |
| 143 | + { multiplier: 15, label: "Spaghettification" }, | |
| 144 | + { multiplier: 60, label: "Event horizon" }, | |
| 145 | + ], | |
| 146 | + presentation: { scene: "blackhole", palette: { primary: "#a78bfa", secondary: "#f0abfc", glow: "#c4b5fd", bg: "#050308", surface: "#120a24" }, verb: "WARP OUT", unit: "km", unitScale: 1000, ambience: "void" }, | |
| 147 | + rules: [...RULES_COMMON, "The curve accelerates sharply near the horizon — the same odds, much faster."], | |
| 148 | +}); | |
| 149 | + | |
| 150 | +export const freefall = defineCrashGame({ | |
| 151 | + slug: "freefall", | |
| 152 | + name: "Freefall", | |
| 153 | + version: "1.0.0", | |
| 154 | + tagline: "Pull late. Not too late.", | |
| 155 | + description: "A skydiver leaps from the edge of the atmosphere. The reward grows during the fall — slowly at altitude, brutally fast near the ground. Open the chute in time or lose the round.", | |
| 156 | + theme: "skydiving", | |
| 157 | + rtp: 0.96, | |
| 158 | + maxMultiplier: 5000, | |
| 159 | + volatility: "high", | |
| 160 | + curve: { type: "power", a: 0.05, p: 2.1 }, | |
| 161 | + events: { chance: 0.25, max: 1, window: [3, 14], kinds: [{ id: "thermal", label: "Thermal", factor: 0.5, duration: [2, 3], weight: 1 }] }, | |
| 162 | + milestones: [ | |
| 163 | + { multiplier: 1.3, label: "Terminal velocity" }, | |
| 164 | + { multiplier: 3, label: "Cloud deck" }, | |
| 165 | + { multiplier: 10, label: "Treetops" }, | |
| 166 | + { multiplier: 40, label: "Ground rush" }, | |
| 167 | + ], | |
| 168 | + presentation: { scene: "freefall", palette: { primary: "#60a5fa", secondary: "#f97316", glow: "#93c5fd", bg: "#07101e", surface: "#0e2140" }, verb: "OPEN CHUTE", unit: "m", unitScale: -300, ambience: "lunar" }, | |
| 169 | + rules: [...RULES_COMMON, "Thermal updrafts slow the fall for a moment — more time to decide."], | |
| 170 | +}); | |
| 171 | + | |
| 172 | +export const coreMeltdown = defineCrashGame({ | |
| 173 | + slug: "core-meltdown", | |
| 174 | + name: "Core Meltdown", | |
| 175 | + version: "1.0.0", | |
| 176 | + tagline: "Stable… stable… SHUT DOWN.", | |
| 177 | + description: "An experimental reactor produces energy as long as it stays stable. Temperature climbs; the multiplier climbs with it. Cooling events can buy you time. Shut down before the limit or lose the batch.", | |
| 178 | + theme: "reactor control room", | |
| 179 | + rtp: 0.96, | |
| 180 | + maxMultiplier: 5000, | |
| 181 | + volatility: "high", | |
| 182 | + curve: { type: "exp", k: 0.07 }, | |
| 183 | + events: { chance: 0.45, max: 2, window: [2, 18], kinds: [{ id: "coolant", label: "Coolant surge", factor: 0.45, duration: [2, 3.5], weight: 3 }, { id: "spike", label: "Power spike", factor: 1.8, duration: [1, 2], weight: 1 }] }, | |
| 184 | + milestones: [ | |
| 185 | + { multiplier: 1.5, label: "Nominal" }, | |
| 186 | + { multiplier: 3, label: "Elevated" }, | |
| 187 | + { multiplier: 8, label: "Critical" }, | |
| 188 | + { multiplier: 25, label: "Containment stress" }, | |
| 189 | + ], | |
| 190 | + presentation: { scene: "reactor", palette: { primary: "#a3e635", secondary: "#facc15", glow: "#bef264", bg: "#070c06", surface: "#111c0e" }, verb: "SHUT DOWN", unit: "°C", unitScale: 90, ambience: "reactor" }, | |
| 191 | + rules: [...RULES_COMMON, "Coolant surges slow the temperature climb; power spikes speed it up."], | |
| 192 | +}); | |
| 193 | + | |
| 194 | +export const stormChase = defineCrashGame({ | |
| 195 | + slug: "storm-chase", | |
| 196 | + name: "Storm Chase", | |
| 197 | + version: "1.0.0", | |
| 198 | + tagline: "Drive into the wall cloud.", | |
| 199 | + description: "You race toward a giant tornado. The closer to the core, the higher the multiplier — but the storm can turn and swallow the truck without warning. ESCAPE while the road is still there.", | |
| 200 | + theme: "storm chasing", | |
| 201 | + rtp: 0.96, | |
| 202 | + maxMultiplier: 5000, | |
| 203 | + volatility: "high", | |
| 204 | + curve: { type: "exp", k: 0.078 }, | |
| 205 | + events: { chance: 0.3, max: 1, window: [3, 15], kinds: [{ id: "gust", label: "Rear-flank gust", factor: 1.9, duration: [1.5, 3], weight: 1 }] }, | |
| 206 | + milestones: [ | |
| 207 | + { multiplier: 1.5, label: "Outflow" }, | |
| 208 | + { multiplier: 3, label: "Debris field" }, | |
| 209 | + { multiplier: 8, label: "Wall cloud" }, | |
| 210 | + { multiplier: 30, label: "Vortex" }, | |
| 211 | + ], | |
| 212 | + presentation: { scene: "storm", palette: { primary: "#94a3b8", secondary: "#a3e635", glow: "#cbd5e1", bg: "#0a0d12", surface: "#1a212c" }, verb: "ESCAPE", unit: "m", unitScale: -400, ambience: "reactor" }, | |
| 213 | + rules: [...RULES_COMMON, "Rear-flank gusts pull you toward the core faster for a moment."], | |
| 214 | +}); | |
| 215 | + | |
| 216 | +export const elevator999 = defineCrashGame({ | |
| 217 | + slug: "elevator-999", | |
| 218 | + name: "Elevator 999", | |
| 219 | + version: "1.0.0", | |
| 220 | + tagline: "Every floor is a decision.", | |
| 221 | + description: "An elevator climbs an endless tower. Each floor raises the multiplier; you may step out and cash out at any floor. The elevator can fail at any moment. Floors 25, 50, 100 and 250 are express zones where the climb accelerates.", | |
| 222 | + theme: "infinite tower", | |
| 223 | + rtp: 0.96, | |
| 224 | + maxMultiplier: 10000, | |
| 225 | + volatility: "high", | |
| 226 | + curve: { type: "steps", stepSeconds: 0.55, base: 1.05 }, | |
| 227 | + milestones: [ | |
| 228 | + { multiplier: Math.pow(1.05, 25), label: "Floor 25 — Sky lobby" }, | |
| 229 | + { multiplier: Math.pow(1.05, 50), label: "Floor 50 — Observation deck" }, | |
| 230 | + { multiplier: Math.pow(1.05, 100), label: "Floor 100 — Cloud gardens" }, | |
| 231 | + { multiplier: Math.pow(1.05, 250), label: "Floor 250 — Stratos suite" }, | |
| 232 | + ], | |
| 233 | + presentation: { scene: "elevator", palette: { primary: "#e8cf8f", secondary: "#38bdf8", glow: "#f3e2ad", bg: "#08090d", surface: "#151821" }, verb: "STEP OUT", unit: "floor", unitScale: 1, ambience: "obsidian" }, | |
| 234 | + rules: [...RULES_COMMON, "The multiplier grows 5% per floor (1.05× compounded). Special floors mark express zones."], | |
| 235 | +}); | |
| 236 | + | |
| 237 | +export const CRASH_GAMES: CrashGameDefinition[] = [skyfall, deepDive, rocketRun, bankHeist, volcano, blackHole, freefall, coreMeltdown, stormChase, elevator999]; | |
| 238 | +export const CRASH_BY_SLUG = new Map(CRASH_GAMES.map((g) => [g.slug, g])); | |
modified
games/src/index.ts
+1 −0
@@ -29,3 +29,4 @@ export function getGame(slug: string): GameDefinition | undefined { | ||
| 29 | 29 | export const FEATURED_ORDER = ["spinza-original", "neon-vault", "cosmic-collapse", "obsidian", "diamond-heist", "quantum-jackpot"]; |
| 30 | 30 | export const FIRST_GAME_RECOMMENDATIONS = ["neon-vault", "cosmic-collapse", "spinza-original"]; |
| 31 | 31 | export { RAW } from "./registry"; |
| 32 | +export { CRASH_GAMES, CRASH_BY_SLUG } from "./crash"; | |
added
packages/database/drizzle/0001_lumpy_hulk.sql
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +CREATE TABLE "crash_rounds" ( | |
| 2 | + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| 3 | + "round_id" varchar(32) NOT NULL, | |
| 4 | + "user_id" uuid NOT NULL, | |
| 5 | + "game_id" uuid NOT NULL, | |
| 6 | + "game_slug" varchar(64) NOT NULL, | |
| 7 | + "game_version" varchar(16) NOT NULL, | |
| 8 | + "client_round_id" uuid NOT NULL, | |
| 9 | + "bet" bigint NOT NULL, | |
| 10 | + "crash_multiplier" numeric(12, 2) NOT NULL, | |
| 11 | + "crash_at_ms" integer NOT NULL, | |
| 12 | + "seed" text NOT NULL, | |
| 13 | + "commitment" varchar(64) NOT NULL, | |
| 14 | + "events" jsonb DEFAULT '[]'::jsonb NOT NULL, | |
| 15 | + "auto_cashout" numeric(12, 2), | |
| 16 | + "status" varchar(12) DEFAULT 'running' NOT NULL, | |
| 17 | + "cashout_multiplier" numeric(12, 2), | |
| 18 | + "win" bigint DEFAULT 0 NOT NULL, | |
| 19 | + "started_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| 20 | + "settled_at" timestamp with time zone | |
| 21 | +); | |
| 22 | +--> statement-breakpoint | |
| 23 | +ALTER TABLE "crash_rounds" ADD CONSTRAINT "crash_rounds_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint | |
| 24 | +ALTER TABLE "crash_rounds" ADD CONSTRAINT "crash_rounds_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint | |
| 25 | +CREATE UNIQUE INDEX "crash_rounds_round_id_idx" ON "crash_rounds" USING btree ("round_id");--> statement-breakpoint | |
| 26 | +CREATE UNIQUE INDEX "crash_rounds_user_client_idx" ON "crash_rounds" USING btree ("user_id","client_round_id");--> statement-breakpoint | |
| 27 | +CREATE INDEX "crash_rounds_user_status_idx" ON "crash_rounds" USING btree ("user_id","status"); | |
| \ No newline at end of file | ||
added
packages/database/drizzle/meta/0001_snapshot.json
+2801 −0
@@ -0,0 +1,2801 @@ | ||
| 1 | +{ | |
| 2 | + "id": "423e47ca-9681-4a28-97a8-d582818cd2d7", | |
| 3 | + "prevId": "ce701f34-3e3b-4c96-a7b0-2d2f90472e21", | |
| 4 | + "version": "7", | |
| 5 | + "dialect": "postgresql", | |
| 6 | + "tables": { | |
| 7 | + "public.achievements": { | |
| 8 | + "name": "achievements", | |
| 9 | + "schema": "", | |
| 10 | + "columns": { | |
| 11 | + "key": { | |
| 12 | + "name": "key", | |
| 13 | + "type": "varchar(48)", | |
| 14 | + "primaryKey": true, | |
| 15 | + "notNull": true | |
| 16 | + }, | |
| 17 | + "name": { | |
| 18 | + "name": "name", | |
| 19 | + "type": "varchar(80)", | |
| 20 | + "primaryKey": false, | |
| 21 | + "notNull": true | |
| 22 | + }, | |
| 23 | + "description": { | |
| 24 | + "name": "description", | |
| 25 | + "type": "text", | |
| 26 | + "primaryKey": false, | |
| 27 | + "notNull": true | |
| 28 | + }, | |
| 29 | + "category": { | |
| 30 | + "name": "category", | |
| 31 | + "type": "varchar(24)", | |
| 32 | + "primaryKey": false, | |
| 33 | + "notNull": true | |
| 34 | + }, | |
| 35 | + "metric": { | |
| 36 | + "name": "metric", | |
| 37 | + "type": "varchar(32)", | |
| 38 | + "primaryKey": false, | |
| 39 | + "notNull": true | |
| 40 | + }, | |
| 41 | + "target": { | |
| 42 | + "name": "target", | |
| 43 | + "type": "bigint", | |
| 44 | + "primaryKey": false, | |
| 45 | + "notNull": true | |
| 46 | + }, | |
| 47 | + "reward_credits": { | |
| 48 | + "name": "reward_credits", | |
| 49 | + "type": "integer", | |
| 50 | + "primaryKey": false, | |
| 51 | + "notNull": true, | |
| 52 | + "default": 0 | |
| 53 | + }, | |
| 54 | + "reward_xp": { | |
| 55 | + "name": "reward_xp", | |
| 56 | + "type": "integer", | |
| 57 | + "primaryKey": false, | |
| 58 | + "notNull": true, | |
| 59 | + "default": 0 | |
| 60 | + }, | |
| 61 | + "icon": { | |
| 62 | + "name": "icon", | |
| 63 | + "type": "varchar(32)", | |
| 64 | + "primaryKey": false, | |
| 65 | + "notNull": true, | |
| 66 | + "default": "'star'" | |
| 67 | + }, | |
| 68 | + "sort_order": { | |
| 69 | + "name": "sort_order", | |
| 70 | + "type": "integer", | |
| 71 | + "primaryKey": false, | |
| 72 | + "notNull": true, | |
| 73 | + "default": 0 | |
| 74 | + }, | |
| 75 | + "enabled": { | |
| 76 | + "name": "enabled", | |
| 77 | + "type": "boolean", | |
| 78 | + "primaryKey": false, | |
| 79 | + "notNull": true, | |
| 80 | + "default": true | |
| 81 | + } | |
| 82 | + }, | |
| 83 | + "indexes": {}, | |
| 84 | + "foreignKeys": {}, | |
| 85 | + "compositePrimaryKeys": {}, | |
| 86 | + "uniqueConstraints": {}, | |
| 87 | + "policies": {}, | |
| 88 | + "checkConstraints": {}, | |
| 89 | + "isRLSEnabled": false | |
| 90 | + }, | |
| 91 | + "public.admin_sessions": { | |
| 92 | + "name": "admin_sessions", | |
| 93 | + "schema": "", | |
| 94 | + "columns": { | |
| 95 | + "id": { | |
| 96 | + "name": "id", | |
| 97 | + "type": "uuid", | |
| 98 | + "primaryKey": true, | |
| 99 | + "notNull": true, | |
| 100 | + "default": "gen_random_uuid()" | |
| 101 | + }, | |
| 102 | + "admin_id": { | |
| 103 | + "name": "admin_id", | |
| 104 | + "type": "uuid", | |
| 105 | + "primaryKey": false, | |
| 106 | + "notNull": true | |
| 107 | + }, | |
| 108 | + "token_hash": { | |
| 109 | + "name": "token_hash", | |
| 110 | + "type": "text", | |
| 111 | + "primaryKey": false, | |
| 112 | + "notNull": true | |
| 113 | + }, | |
| 114 | + "created_at": { | |
| 115 | + "name": "created_at", | |
| 116 | + "type": "timestamp with time zone", | |
| 117 | + "primaryKey": false, | |
| 118 | + "notNull": true, | |
| 119 | + "default": "now()" | |
| 120 | + }, | |
| 121 | + "expires_at": { | |
| 122 | + "name": "expires_at", | |
| 123 | + "type": "timestamp with time zone", | |
| 124 | + "primaryKey": false, | |
| 125 | + "notNull": true | |
| 126 | + }, | |
| 127 | + "ip": { | |
| 128 | + "name": "ip", | |
| 129 | + "type": "varchar(64)", | |
| 130 | + "primaryKey": false, | |
| 131 | + "notNull": false | |
| 132 | + } | |
| 133 | + }, | |
| 134 | + "indexes": { | |
| 135 | + "admin_sessions_token_idx": { | |
| 136 | + "name": "admin_sessions_token_idx", | |
| 137 | + "columns": [ | |
| 138 | + { | |
| 139 | + "expression": "token_hash", | |
| 140 | + "isExpression": false, | |
| 141 | + "asc": true, | |
| 142 | + "nulls": "last" | |
| 143 | + } | |
| 144 | + ], | |
| 145 | + "isUnique": true, | |
| 146 | + "concurrently": false, | |
| 147 | + "method": "btree", | |
| 148 | + "with": {} | |
| 149 | + } | |
| 150 | + }, | |
| 151 | + "foreignKeys": { | |
| 152 | + "admin_sessions_admin_id_admin_users_id_fk": { | |
| 153 | + "name": "admin_sessions_admin_id_admin_users_id_fk", | |
| 154 | + "tableFrom": "admin_sessions", | |
| 155 | + "tableTo": "admin_users", | |
| 156 | + "columnsFrom": [ | |
| 157 | + "admin_id" | |
| 158 | + ], | |
| 159 | + "columnsTo": [ | |
| 160 | + "id" | |
| 161 | + ], | |
| 162 | + "onDelete": "cascade", | |
| 163 | + "onUpdate": "no action" | |
| 164 | + } | |
| 165 | + }, | |
| 166 | + "compositePrimaryKeys": {}, | |
| 167 | + "uniqueConstraints": {}, | |
| 168 | + "policies": {}, | |
| 169 | + "checkConstraints": {}, | |
| 170 | + "isRLSEnabled": false | |
| 171 | + }, | |
| 172 | + "public.admin_users": { | |
| 173 | + "name": "admin_users", | |
| 174 | + "schema": "", | |
| 175 | + "columns": { | |
| 176 | + "id": { | |
| 177 | + "name": "id", | |
| 178 | + "type": "uuid", | |
| 179 | + "primaryKey": true, | |
| 180 | + "notNull": true, | |
| 181 | + "default": "gen_random_uuid()" | |
| 182 | + }, | |
| 183 | + "username": { | |
| 184 | + "name": "username", | |
| 185 | + "type": "varchar(32)", | |
| 186 | + "primaryKey": false, | |
| 187 | + "notNull": true | |
| 188 | + }, | |
| 189 | + "password_hash": { | |
| 190 | + "name": "password_hash", | |
| 191 | + "type": "text", | |
| 192 | + "primaryKey": false, | |
| 193 | + "notNull": true | |
| 194 | + }, | |
| 195 | + "totp_secret": { | |
| 196 | + "name": "totp_secret", | |
| 197 | + "type": "text", | |
| 198 | + "primaryKey": false, | |
| 199 | + "notNull": true | |
| 200 | + }, | |
| 201 | + "role": { | |
| 202 | + "name": "role", | |
| 203 | + "type": "varchar(16)", | |
| 204 | + "primaryKey": false, | |
| 205 | + "notNull": true, | |
| 206 | + "default": "'admin'" | |
| 207 | + }, | |
| 208 | + "created_at": { | |
| 209 | + "name": "created_at", | |
| 210 | + "type": "timestamp with time zone", | |
| 211 | + "primaryKey": false, | |
| 212 | + "notNull": true, | |
| 213 | + "default": "now()" | |
| 214 | + }, | |
| 215 | + "last_login_at": { | |
| 216 | + "name": "last_login_at", | |
| 217 | + "type": "timestamp with time zone", | |
| 218 | + "primaryKey": false, | |
| 219 | + "notNull": false | |
| 220 | + }, | |
| 221 | + "disabled": { | |
| 222 | + "name": "disabled", | |
| 223 | + "type": "boolean", | |
| 224 | + "primaryKey": false, | |
| 225 | + "notNull": true, | |
| 226 | + "default": false | |
| 227 | + } | |
| 228 | + }, | |
| 229 | + "indexes": { | |
| 230 | + "admin_users_username_idx": { | |
| 231 | + "name": "admin_users_username_idx", | |
| 232 | + "columns": [ | |
| 233 | + { | |
| 234 | + "expression": "username", | |
| 235 | + "isExpression": false, | |
| 236 | + "asc": true, | |
| 237 | + "nulls": "last" | |
| 238 | + } | |
| 239 | + ], | |
| 240 | + "isUnique": true, | |
| 241 | + "concurrently": false, | |
| 242 | + "method": "btree", | |
| 243 | + "with": {} | |
| 244 | + } | |
| 245 | + }, | |
| 246 | + "foreignKeys": {}, | |
| 247 | + "compositePrimaryKeys": {}, | |
| 248 | + "uniqueConstraints": {}, | |
| 249 | + "policies": {}, | |
| 250 | + "checkConstraints": {}, | |
| 251 | + "isRLSEnabled": false | |
| 252 | + }, | |
| 253 | + "public.crash_rounds": { | |
| 254 | + "name": "crash_rounds", | |
| 255 | + "schema": "", | |
| 256 | + "columns": { | |
| 257 | + "id": { | |
| 258 | + "name": "id", | |
| 259 | + "type": "uuid", | |
| 260 | + "primaryKey": true, | |
| 261 | + "notNull": true, | |
| 262 | + "default": "gen_random_uuid()" | |
| 263 | + }, | |
| 264 | + "round_id": { | |
| 265 | + "name": "round_id", | |
| 266 | + "type": "varchar(32)", | |
| 267 | + "primaryKey": false, | |
| 268 | + "notNull": true | |
| 269 | + }, | |
| 270 | + "user_id": { | |
| 271 | + "name": "user_id", | |
| 272 | + "type": "uuid", | |
| 273 | + "primaryKey": false, | |
| 274 | + "notNull": true | |
| 275 | + }, | |
| 276 | + "game_id": { | |
| 277 | + "name": "game_id", | |
| 278 | + "type": "uuid", | |
| 279 | + "primaryKey": false, | |
| 280 | + "notNull": true | |
| 281 | + }, | |
| 282 | + "game_slug": { | |
| 283 | + "name": "game_slug", | |
| 284 | + "type": "varchar(64)", | |
| 285 | + "primaryKey": false, | |
| 286 | + "notNull": true | |
| 287 | + }, | |
| 288 | + "game_version": { | |
| 289 | + "name": "game_version", | |
| 290 | + "type": "varchar(16)", | |
| 291 | + "primaryKey": false, | |
| 292 | + "notNull": true | |
| 293 | + }, | |
| 294 | + "client_round_id": { | |
| 295 | + "name": "client_round_id", | |
| 296 | + "type": "uuid", | |
| 297 | + "primaryKey": false, | |
| 298 | + "notNull": true | |
| 299 | + }, | |
| 300 | + "bet": { | |
| 301 | + "name": "bet", | |
| 302 | + "type": "bigint", | |
| 303 | + "primaryKey": false, | |
| 304 | + "notNull": true | |
| 305 | + }, | |
| 306 | + "crash_multiplier": { | |
| 307 | + "name": "crash_multiplier", | |
| 308 | + "type": "numeric(12, 2)", | |
| 309 | + "primaryKey": false, | |
| 310 | + "notNull": true | |
| 311 | + }, | |
| 312 | + "crash_at_ms": { | |
| 313 | + "name": "crash_at_ms", | |
| 314 | + "type": "integer", | |
| 315 | + "primaryKey": false, | |
| 316 | + "notNull": true | |
| 317 | + }, | |
| 318 | + "seed": { | |
| 319 | + "name": "seed", | |
| 320 | + "type": "text", | |
| 321 | + "primaryKey": false, | |
| 322 | + "notNull": true | |
| 323 | + }, | |
| 324 | + "commitment": { | |
| 325 | + "name": "commitment", | |
| 326 | + "type": "varchar(64)", | |
| 327 | + "primaryKey": false, | |
| 328 | + "notNull": true | |
| 329 | + }, | |
| 330 | + "events": { | |
| 331 | + "name": "events", | |
| 332 | + "type": "jsonb", | |
| 333 | + "primaryKey": false, | |
| 334 | + "notNull": true, | |
| 335 | + "default": "'[]'::jsonb" | |
| 336 | + }, | |
| 337 | + "auto_cashout": { | |
| 338 | + "name": "auto_cashout", | |
| 339 | + "type": "numeric(12, 2)", | |
| 340 | + "primaryKey": false, | |
| 341 | + "notNull": false | |
| 342 | + }, | |
| 343 | + "status": { | |
| 344 | + "name": "status", | |
| 345 | + "type": "varchar(12)", | |
| 346 | + "primaryKey": false, | |
| 347 | + "notNull": true, | |
| 348 | + "default": "'running'" | |
| 349 | + }, | |
| 350 | + "cashout_multiplier": { | |
| 351 | + "name": "cashout_multiplier", | |
| 352 | + "type": "numeric(12, 2)", | |
| 353 | + "primaryKey": false, | |
| 354 | + "notNull": false | |
| 355 | + }, | |
| 356 | + "win": { | |
| 357 | + "name": "win", | |
| 358 | + "type": "bigint", | |
| 359 | + "primaryKey": false, | |
| 360 | + "notNull": true, | |
| 361 | + "default": 0 | |
| 362 | + }, | |
| 363 | + "started_at": { | |
| 364 | + "name": "started_at", | |
| 365 | + "type": "timestamp with time zone", | |
| 366 | + "primaryKey": false, | |
| 367 | + "notNull": true, | |
| 368 | + "default": "now()" | |
| 369 | + }, | |
| 370 | + "settled_at": { | |
| 371 | + "name": "settled_at", | |
| 372 | + "type": "timestamp with time zone", | |
| 373 | + "primaryKey": false, | |
| 374 | + "notNull": false | |
| 375 | + } | |
| 376 | + }, | |
| 377 | + "indexes": { | |
| 378 | + "crash_rounds_round_id_idx": { | |
| 379 | + "name": "crash_rounds_round_id_idx", | |
| 380 | + "columns": [ | |
| 381 | + { | |
| 382 | + "expression": "round_id", | |
| 383 | + "isExpression": false, | |
| 384 | + "asc": true, | |
| 385 | + "nulls": "last" | |
| 386 | + } | |
| 387 | + ], | |
| 388 | + "isUnique": true, | |
| 389 | + "concurrently": false, | |
| 390 | + "method": "btree", | |
| 391 | + "with": {} | |
| 392 | + }, | |
| 393 | + "crash_rounds_user_client_idx": { | |
| 394 | + "name": "crash_rounds_user_client_idx", | |
| 395 | + "columns": [ | |
| 396 | + { | |
| 397 | + "expression": "user_id", | |
| 398 | + "isExpression": false, | |
| 399 | + "asc": true, | |
| 400 | + "nulls": "last" | |
| 401 | + }, | |
| 402 | + { | |
| 403 | + "expression": "client_round_id", | |
| 404 | + "isExpression": false, | |
| 405 | + "asc": true, | |
| 406 | + "nulls": "last" | |
| 407 | + } | |
| 408 | + ], | |
| 409 | + "isUnique": true, | |
| 410 | + "concurrently": false, | |
| 411 | + "method": "btree", | |
| 412 | + "with": {} | |
| 413 | + }, | |
| 414 | + "crash_rounds_user_status_idx": { | |
| 415 | + "name": "crash_rounds_user_status_idx", | |
| 416 | + "columns": [ | |
| 417 | + { | |
| 418 | + "expression": "user_id", | |
| 419 | + "isExpression": false, | |
| 420 | + "asc": true, | |
| 421 | + "nulls": "last" | |
| 422 | + }, | |
| 423 | + { | |
| 424 | + "expression": "status", | |
| 425 | + "isExpression": false, | |
| 426 | + "asc": true, | |
| 427 | + "nulls": "last" | |
| 428 | + } | |
| 429 | + ], | |
| 430 | + "isUnique": false, | |
| 431 | + "concurrently": false, | |
| 432 | + "method": "btree", | |
| 433 | + "with": {} | |
| 434 | + } | |
| 435 | + }, | |
| 436 | + "foreignKeys": { | |
| 437 | + "crash_rounds_user_id_users_id_fk": { | |
| 438 | + "name": "crash_rounds_user_id_users_id_fk", | |
| 439 | + "tableFrom": "crash_rounds", | |
| 440 | + "tableTo": "users", | |
| 441 | + "columnsFrom": [ | |
| 442 | + "user_id" | |
| 443 | + ], | |
| 444 | + "columnsTo": [ | |
| 445 | + "id" | |
| 446 | + ], | |
| 447 | + "onDelete": "cascade", | |
| 448 | + "onUpdate": "no action" | |
| 449 | + }, | |
| 450 | + "crash_rounds_game_id_games_id_fk": { | |
| 451 | + "name": "crash_rounds_game_id_games_id_fk", | |
| 452 | + "tableFrom": "crash_rounds", | |
| 453 | + "tableTo": "games", | |
| 454 | + "columnsFrom": [ | |
| 455 | + "game_id" | |
| 456 | + ], | |
| 457 | + "columnsTo": [ | |
| 458 | + "id" | |
| 459 | + ], | |
| 460 | + "onDelete": "no action", | |
| 461 | + "onUpdate": "no action" | |
| 462 | + } | |
| 463 | + }, | |
| 464 | + "compositePrimaryKeys": {}, | |
| 465 | + "uniqueConstraints": {}, | |
| 466 | + "policies": {}, | |
| 467 | + "checkConstraints": {}, | |
| 468 | + "isRLSEnabled": false | |
| 469 | + }, | |
| 470 | + "public.credit_transactions": { | |
| 471 | + "name": "credit_transactions", | |
| 472 | + "schema": "", | |
| 473 | + "columns": { | |
| 474 | + "id": { | |
| 475 | + "name": "id", | |
| 476 | + "type": "uuid", | |
| 477 | + "primaryKey": true, | |
| 478 | + "notNull": true, | |
| 479 | + "default": "gen_random_uuid()" | |
| 480 | + }, | |
| 481 | + "user_id": { | |
| 482 | + "name": "user_id", | |
| 483 | + "type": "uuid", | |
| 484 | + "primaryKey": false, | |
| 485 | + "notNull": true | |
| 486 | + }, | |
| 487 | + "type": { | |
| 488 | + "name": "type", | |
| 489 | + "type": "varchar(24)", | |
| 490 | + "primaryKey": false, | |
| 491 | + "notNull": true | |
| 492 | + }, | |
| 493 | + "amount": { | |
| 494 | + "name": "amount", | |
| 495 | + "type": "bigint", | |
| 496 | + "primaryKey": false, | |
| 497 | + "notNull": true | |
| 498 | + }, | |
| 499 | + "balance_after": { | |
| 500 | + "name": "balance_after", | |
| 501 | + "type": "bigint", | |
| 502 | + "primaryKey": false, | |
| 503 | + "notNull": true | |
| 504 | + }, | |
| 505 | + "reference": { | |
| 506 | + "name": "reference", | |
| 507 | + "type": "text", | |
| 508 | + "primaryKey": false, | |
| 509 | + "notNull": false | |
| 510 | + }, | |
| 511 | + "meta": { | |
| 512 | + "name": "meta", | |
| 513 | + "type": "jsonb", | |
| 514 | + "primaryKey": false, | |
| 515 | + "notNull": false | |
| 516 | + }, | |
| 517 | + "created_at": { | |
| 518 | + "name": "created_at", | |
| 519 | + "type": "timestamp with time zone", | |
| 520 | + "primaryKey": false, | |
| 521 | + "notNull": true, | |
| 522 | + "default": "now()" | |
| 523 | + } | |
| 524 | + }, | |
| 525 | + "indexes": { | |
| 526 | + "ctx_user_created_idx": { | |
| 527 | + "name": "ctx_user_created_idx", | |
| 528 | + "columns": [ | |
| 529 | + { | |
| 530 | + "expression": "user_id", | |
| 531 | + "isExpression": false, | |
| 532 | + "asc": true, | |
| 533 | + "nulls": "last" | |
| 534 | + }, | |
| 535 | + { | |
| 536 | + "expression": "created_at", | |
| 537 | + "isExpression": false, | |
| 538 | + "asc": true, | |
| 539 | + "nulls": "last" | |
| 540 | + } | |
| 541 | + ], | |
| 542 | + "isUnique": false, | |
| 543 | + "concurrently": false, | |
| 544 | + "method": "btree", | |
| 545 | + "with": {} | |
| 546 | + }, | |
| 547 | + "ctx_type_idx": { | |
| 548 | + "name": "ctx_type_idx", | |
| 549 | + "columns": [ | |
| 550 | + { | |
| 551 | + "expression": "type", | |
| 552 | + "isExpression": false, | |
| 553 | + "asc": true, | |
| 554 | + "nulls": "last" | |
| 555 | + } | |
| 556 | + ], | |
| 557 | + "isUnique": false, | |
| 558 | + "concurrently": false, | |
| 559 | + "method": "btree", | |
| 560 | + "with": {} | |
| 561 | + }, | |
| 562 | + "ctx_reference_idx": { | |
| 563 | + "name": "ctx_reference_idx", | |
| 564 | + "columns": [ | |
| 565 | + { | |
| 566 | + "expression": "reference", | |
| 567 | + "isExpression": false, | |
| 568 | + "asc": true, | |
| 569 | + "nulls": "last" | |
| 570 | + } | |
| 571 | + ], | |
| 572 | + "isUnique": false, | |
| 573 | + "concurrently": false, | |
| 574 | + "method": "btree", | |
| 575 | + "with": {} | |
| 576 | + } | |
| 577 | + }, | |
| 578 | + "foreignKeys": { | |
| 579 | + "credit_transactions_user_id_users_id_fk": { | |
| 580 | + "name": "credit_transactions_user_id_users_id_fk", | |
| 581 | + "tableFrom": "credit_transactions", | |
| 582 | + "tableTo": "users", | |
| 583 | + "columnsFrom": [ | |
| 584 | + "user_id" | |
| 585 | + ], | |
| 586 | + "columnsTo": [ | |
| 587 | + "id" | |
| 588 | + ], | |
| 589 | + "onDelete": "cascade", | |
| 590 | + "onUpdate": "no action" | |
| 591 | + } | |
| 592 | + }, | |
| 593 | + "compositePrimaryKeys": {}, | |
| 594 | + "uniqueConstraints": {}, | |
| 595 | + "policies": {}, | |
| 596 | + "checkConstraints": {}, | |
| 597 | + "isRLSEnabled": false | |
| 598 | + }, | |
| 599 | + "public.daily_rewards": { | |
| 600 | + "name": "daily_rewards", | |
| 601 | + "schema": "", | |
| 602 | + "columns": { | |
| 603 | + "user_id": { | |
| 604 | + "name": "user_id", | |
| 605 | + "type": "uuid", | |
| 606 | + "primaryKey": true, | |
| 607 | + "notNull": true | |
| 608 | + }, | |
| 609 | + "streak_day": { | |
| 610 | + "name": "streak_day", | |
| 611 | + "type": "integer", | |
| 612 | + "primaryKey": false, | |
| 613 | + "notNull": true, | |
| 614 | + "default": 0 | |
| 615 | + }, | |
| 616 | + "last_claimed_at": { | |
| 617 | + "name": "last_claimed_at", | |
| 618 | + "type": "timestamp with time zone", | |
| 619 | + "primaryKey": false, | |
| 620 | + "notNull": false | |
| 621 | + }, | |
| 622 | + "next_available_at": { | |
| 623 | + "name": "next_available_at", | |
| 624 | + "type": "timestamp with time zone", | |
| 625 | + "primaryKey": false, | |
| 626 | + "notNull": false | |
| 627 | + }, | |
| 628 | + "total_claimed": { | |
| 629 | + "name": "total_claimed", | |
| 630 | + "type": "bigint", | |
| 631 | + "primaryKey": false, | |
| 632 | + "notNull": true, | |
| 633 | + "default": 0 | |
| 634 | + }, | |
| 635 | + "claims": { | |
| 636 | + "name": "claims", | |
| 637 | + "type": "integer", | |
| 638 | + "primaryKey": false, | |
| 639 | + "notNull": true, | |
| 640 | + "default": 0 | |
| 641 | + } | |
| 642 | + }, | |
| 643 | + "indexes": {}, | |
| 644 | + "foreignKeys": { | |
| 645 | + "daily_rewards_user_id_users_id_fk": { | |
| 646 | + "name": "daily_rewards_user_id_users_id_fk", | |
| 647 | + "tableFrom": "daily_rewards", | |
| 648 | + "tableTo": "users", | |
| 649 | + "columnsFrom": [ | |
| 650 | + "user_id" | |
| 651 | + ], | |
| 652 | + "columnsTo": [ | |
| 653 | + "id" | |
| 654 | + ], | |
| 655 | + "onDelete": "cascade", | |
| 656 | + "onUpdate": "no action" | |
| 657 | + } | |
| 658 | + }, | |
| 659 | + "compositePrimaryKeys": {}, | |
| 660 | + "uniqueConstraints": {}, | |
| 661 | + "policies": {}, | |
| 662 | + "checkConstraints": {}, | |
| 663 | + "isRLSEnabled": false | |
| 664 | + }, | |
| 665 | + "public.favorites": { | |
| 666 | + "name": "favorites", | |
| 667 | + "schema": "", | |
| 668 | + "columns": { | |
| 669 | + "user_id": { | |
| 670 | + "name": "user_id", | |
| 671 | + "type": "uuid", | |
| 672 | + "primaryKey": false, | |
| 673 | + "notNull": true | |
| 674 | + }, | |
| 675 | + "game_id": { | |
| 676 | + "name": "game_id", | |
| 677 | + "type": "uuid", | |
| 678 | + "primaryKey": false, | |
| 679 | + "notNull": true | |
| 680 | + }, | |
| 681 | + "created_at": { | |
| 682 | + "name": "created_at", | |
| 683 | + "type": "timestamp with time zone", | |
| 684 | + "primaryKey": false, | |
| 685 | + "notNull": true, | |
| 686 | + "default": "now()" | |
| 687 | + } | |
| 688 | + }, | |
| 689 | + "indexes": {}, | |
| 690 | + "foreignKeys": { | |
| 691 | + "favorites_user_id_users_id_fk": { | |
| 692 | + "name": "favorites_user_id_users_id_fk", | |
| 693 | + "tableFrom": "favorites", | |
| 694 | + "tableTo": "users", | |
| 695 | + "columnsFrom": [ | |
| 696 | + "user_id" | |
| 697 | + ], | |
| 698 | + "columnsTo": [ | |
| 699 | + "id" | |
| 700 | + ], | |
| 701 | + "onDelete": "cascade", | |
| 702 | + "onUpdate": "no action" | |
| 703 | + }, | |
| 704 | + "favorites_game_id_games_id_fk": { | |
| 705 | + "name": "favorites_game_id_games_id_fk", | |
| 706 | + "tableFrom": "favorites", | |
| 707 | + "tableTo": "games", | |
| 708 | + "columnsFrom": [ | |
| 709 | + "game_id" | |
| 710 | + ], | |
| 711 | + "columnsTo": [ | |
| 712 | + "id" | |
| 713 | + ], | |
| 714 | + "onDelete": "cascade", | |
| 715 | + "onUpdate": "no action" | |
| 716 | + } | |
| 717 | + }, | |
| 718 | + "compositePrimaryKeys": { | |
| 719 | + "favorites_user_id_game_id_pk": { | |
| 720 | + "name": "favorites_user_id_game_id_pk", | |
| 721 | + "columns": [ | |
| 722 | + "user_id", | |
| 723 | + "game_id" | |
| 724 | + ] | |
| 725 | + } | |
| 726 | + }, | |
| 727 | + "uniqueConstraints": {}, | |
| 728 | + "policies": {}, | |
| 729 | + "checkConstraints": {}, | |
| 730 | + "isRLSEnabled": false | |
| 731 | + }, | |
| 732 | + "public.feature_flags": { | |
| 733 | + "name": "feature_flags", | |
| 734 | + "schema": "", | |
| 735 | + "columns": { | |
| 736 | + "key": { | |
| 737 | + "name": "key", | |
| 738 | + "type": "varchar(64)", | |
| 739 | + "primaryKey": true, | |
| 740 | + "notNull": true | |
| 741 | + }, | |
| 742 | + "enabled": { | |
| 743 | + "name": "enabled", | |
| 744 | + "type": "boolean", | |
| 745 | + "primaryKey": false, | |
| 746 | + "notNull": true, | |
| 747 | + "default": true | |
| 748 | + }, | |
| 749 | + "description": { | |
| 750 | + "name": "description", | |
| 751 | + "type": "text", | |
| 752 | + "primaryKey": false, | |
| 753 | + "notNull": false | |
| 754 | + }, | |
| 755 | + "updated_at": { | |
| 756 | + "name": "updated_at", | |
| 757 | + "type": "timestamp with time zone", | |
| 758 | + "primaryKey": false, | |
| 759 | + "notNull": true, | |
| 760 | + "default": "now()" | |
| 761 | + } | |
| 762 | + }, | |
| 763 | + "indexes": {}, | |
| 764 | + "foreignKeys": {}, | |
| 765 | + "compositePrimaryKeys": {}, | |
| 766 | + "uniqueConstraints": {}, | |
| 767 | + "policies": {}, | |
| 768 | + "checkConstraints": {}, | |
| 769 | + "isRLSEnabled": false | |
| 770 | + }, | |
| 771 | + "public.game_rounds": { | |
| 772 | + "name": "game_rounds", | |
| 773 | + "schema": "", | |
| 774 | + "columns": { | |
| 775 | + "id": { | |
| 776 | + "name": "id", | |
| 777 | + "type": "uuid", | |
| 778 | + "primaryKey": true, | |
| 779 | + "notNull": true, | |
| 780 | + "default": "gen_random_uuid()" | |
| 781 | + }, | |
| 782 | + "round_id": { | |
| 783 | + "name": "round_id", | |
| 784 | + "type": "varchar(32)", | |
| 785 | + "primaryKey": false, | |
| 786 | + "notNull": true | |
| 787 | + }, | |
| 788 | + "user_id": { | |
| 789 | + "name": "user_id", | |
| 790 | + "type": "uuid", | |
| 791 | + "primaryKey": false, | |
| 792 | + "notNull": true | |
| 793 | + }, | |
| 794 | + "game_id": { | |
| 795 | + "name": "game_id", | |
| 796 | + "type": "uuid", | |
| 797 | + "primaryKey": false, | |
| 798 | + "notNull": true | |
| 799 | + }, | |
| 800 | + "game_slug": { | |
| 801 | + "name": "game_slug", | |
| 802 | + "type": "varchar(64)", | |
| 803 | + "primaryKey": false, | |
| 804 | + "notNull": true | |
| 805 | + }, | |
| 806 | + "game_version": { | |
| 807 | + "name": "game_version", | |
| 808 | + "type": "varchar(16)", | |
| 809 | + "primaryKey": false, | |
| 810 | + "notNull": true | |
| 811 | + }, | |
| 812 | + "client_round_id": { | |
| 813 | + "name": "client_round_id", | |
| 814 | + "type": "uuid", | |
| 815 | + "primaryKey": false, | |
| 816 | + "notNull": true | |
| 817 | + }, | |
| 818 | + "bet": { | |
| 819 | + "name": "bet", | |
| 820 | + "type": "bigint", | |
| 821 | + "primaryKey": false, | |
| 822 | + "notNull": true | |
| 823 | + }, | |
| 824 | + "win": { | |
| 825 | + "name": "win", | |
| 826 | + "type": "bigint", | |
| 827 | + "primaryKey": false, | |
| 828 | + "notNull": true | |
| 829 | + }, | |
| 830 | + "multiplier": { | |
| 831 | + "name": "multiplier", | |
| 832 | + "type": "numeric(12, 4)", | |
| 833 | + "primaryKey": false, | |
| 834 | + "notNull": true | |
| 835 | + }, | |
| 836 | + "balance_after": { | |
| 837 | + "name": "balance_after", | |
| 838 | + "type": "bigint", | |
| 839 | + "primaryKey": false, | |
| 840 | + "notNull": true | |
| 841 | + }, | |
| 842 | + "result": { | |
| 843 | + "name": "result", | |
| 844 | + "type": "jsonb", | |
| 845 | + "primaryKey": false, | |
| 846 | + "notNull": true | |
| 847 | + }, | |
| 848 | + "features": { | |
| 849 | + "name": "features", | |
| 850 | + "type": "text[]", | |
| 851 | + "primaryKey": false, | |
| 852 | + "notNull": true, | |
| 853 | + "default": "'{}'::text[]" | |
| 854 | + }, | |
| 855 | + "free_spins": { | |
| 856 | + "name": "free_spins", | |
| 857 | + "type": "boolean", | |
| 858 | + "primaryKey": false, | |
| 859 | + "notNull": true, | |
| 860 | + "default": false | |
| 861 | + }, | |
| 862 | + "bonus": { | |
| 863 | + "name": "bonus", | |
| 864 | + "type": "boolean", | |
| 865 | + "primaryKey": false, | |
| 866 | + "notNull": true, | |
| 867 | + "default": false | |
| 868 | + }, | |
| 869 | + "jackpot_tier": { | |
| 870 | + "name": "jackpot_tier", | |
| 871 | + "type": "varchar(8)", | |
| 872 | + "primaryKey": false, | |
| 873 | + "notNull": false | |
| 874 | + }, | |
| 875 | + "rng_reference": { | |
| 876 | + "name": "rng_reference", | |
| 877 | + "type": "text", | |
| 878 | + "primaryKey": false, | |
| 879 | + "notNull": true | |
| 880 | + }, | |
| 881 | + "duration_ms": { | |
| 882 | + "name": "duration_ms", | |
| 883 | + "type": "integer", | |
| 884 | + "primaryKey": false, | |
| 885 | + "notNull": false | |
| 886 | + }, | |
| 887 | + "created_at": { | |
| 888 | + "name": "created_at", | |
| 889 | + "type": "timestamp with time zone", | |
| 890 | + "primaryKey": false, | |
| 891 | + "notNull": true, | |
| 892 | + "default": "now()" | |
| 893 | + } | |
| 894 | + }, | |
| 895 | + "indexes": { | |
| 896 | + "game_rounds_round_id_idx": { | |
| 897 | + "name": "game_rounds_round_id_idx", | |
| 898 | + "columns": [ | |
| 899 | + { | |
| 900 | + "expression": "round_id", | |
| 901 | + "isExpression": false, | |
| 902 | + "asc": true, | |
| 903 | + "nulls": "last" | |
| 904 | + } | |
| 905 | + ], | |
| 906 | + "isUnique": true, | |
| 907 | + "concurrently": false, | |
| 908 | + "method": "btree", | |
| 909 | + "with": {} | |
| 910 | + }, | |
| 911 | + "game_rounds_user_client_idx": { | |
| 912 | + "name": "game_rounds_user_client_idx", | |
| 913 | + "columns": [ | |
| 914 | + { | |
| 915 | + "expression": "user_id", | |
| 916 | + "isExpression": false, | |
| 917 | + "asc": true, | |
| 918 | + "nulls": "last" | |
| 919 | + }, | |
| 920 | + { | |
| 921 | + "expression": "client_round_id", | |
| 922 | + "isExpression": false, | |
| 923 | + "asc": true, | |
| 924 | + "nulls": "last" | |
| 925 | + } | |
| 926 | + ], | |
| 927 | + "isUnique": true, | |
| 928 | + "concurrently": false, | |
| 929 | + "method": "btree", | |
| 930 | + "with": {} | |
| 931 | + }, | |
| 932 | + "game_rounds_user_created_idx": { | |
| 933 | + "name": "game_rounds_user_created_idx", | |
| 934 | + "columns": [ | |
| 935 | + { | |
| 936 | + "expression": "user_id", | |
| 937 | + "isExpression": false, | |
| 938 | + "asc": true, | |
| 939 | + "nulls": "last" | |
| 940 | + }, | |
| 941 | + { | |
| 942 | + "expression": "created_at", | |
| 943 | + "isExpression": false, | |
| 944 | + "asc": true, | |
| 945 | + "nulls": "last" | |
| 946 | + } | |
| 947 | + ], | |
| 948 | + "isUnique": false, | |
| 949 | + "concurrently": false, | |
| 950 | + "method": "btree", | |
| 951 | + "with": {} | |
| 952 | + }, | |
| 953 | + "game_rounds_game_created_idx": { | |
| 954 | + "name": "game_rounds_game_created_idx", | |
| 955 | + "columns": [ | |
| 956 | + { | |
| 957 | + "expression": "game_id", | |
| 958 | + "isExpression": false, | |
| 959 | + "asc": true, | |
| 960 | + "nulls": "last" | |
| 961 | + }, | |
| 962 | + { | |
| 963 | + "expression": "created_at", | |
| 964 | + "isExpression": false, | |
| 965 | + "asc": true, | |
| 966 | + "nulls": "last" | |
| 967 | + } | |
| 968 | + ], | |
| 969 | + "isUnique": false, | |
| 970 | + "concurrently": false, | |
| 971 | + "method": "btree", | |
| 972 | + "with": {} | |
| 973 | + }, | |
| 974 | + "game_rounds_created_idx": { | |
| 975 | + "name": "game_rounds_created_idx", | |
| 976 | + "columns": [ | |
| 977 | + { | |
| 978 | + "expression": "created_at", | |
| 979 | + "isExpression": false, | |
| 980 | + "asc": true, | |
| 981 | + "nulls": "last" | |
| 982 | + } | |
| 983 | + ], | |
| 984 | + "isUnique": false, | |
| 985 | + "concurrently": false, | |
| 986 | + "method": "btree", | |
| 987 | + "with": {} | |
| 988 | + }, | |
| 989 | + "game_rounds_win_idx": { | |
| 990 | + "name": "game_rounds_win_idx", | |
| 991 | + "columns": [ | |
| 992 | + { | |
| 993 | + "expression": "win", | |
| 994 | + "isExpression": false, | |
| 995 | + "asc": true, | |
| 996 | + "nulls": "last" | |
| 997 | + } | |
| 998 | + ], | |
| 999 | + "isUnique": false, | |
| 1000 | + "concurrently": false, | |
| 1001 | + "method": "btree", | |
| 1002 | + "with": {} | |
| 1003 | + } | |
| 1004 | + }, | |
| 1005 | + "foreignKeys": { | |
| 1006 | + "game_rounds_user_id_users_id_fk": { | |
| 1007 | + "name": "game_rounds_user_id_users_id_fk", | |
| 1008 | + "tableFrom": "game_rounds", | |
| 1009 | + "tableTo": "users", | |
| 1010 | + "columnsFrom": [ | |
| 1011 | + "user_id" | |
| 1012 | + ], | |
| 1013 | + "columnsTo": [ | |
| 1014 | + "id" | |
| 1015 | + ], | |
| 1016 | + "onDelete": "cascade", | |
| 1017 | + "onUpdate": "no action" | |
| 1018 | + }, | |
| 1019 | + "game_rounds_game_id_games_id_fk": { | |
| 1020 | + "name": "game_rounds_game_id_games_id_fk", | |
| 1021 | + "tableFrom": "game_rounds", | |
| 1022 | + "tableTo": "games", | |
| 1023 | + "columnsFrom": [ | |
| 1024 | + "game_id" | |
| 1025 | + ], | |
| 1026 | + "columnsTo": [ | |
| 1027 | + "id" | |
| 1028 | + ], | |
| 1029 | + "onDelete": "no action", | |
| 1030 | + "onUpdate": "no action" | |
| 1031 | + } | |
| 1032 | + }, | |
| 1033 | + "compositePrimaryKeys": {}, | |
| 1034 | + "uniqueConstraints": {}, | |
| 1035 | + "policies": {}, | |
| 1036 | + "checkConstraints": {}, | |
| 1037 | + "isRLSEnabled": false | |
| 1038 | + }, | |
| 1039 | + "public.game_states": { | |
| 1040 | + "name": "game_states", | |
| 1041 | + "schema": "", | |
| 1042 | + "columns": { | |
| 1043 | + "user_id": { | |
| 1044 | + "name": "user_id", | |
| 1045 | + "type": "uuid", | |
| 1046 | + "primaryKey": false, | |
| 1047 | + "notNull": true | |
| 1048 | + }, | |
| 1049 | + "game_id": { | |
| 1050 | + "name": "game_id", | |
| 1051 | + "type": "uuid", | |
| 1052 | + "primaryKey": false, | |
| 1053 | + "notNull": true | |
| 1054 | + }, | |
| 1055 | + "state": { | |
| 1056 | + "name": "state", | |
| 1057 | + "type": "jsonb", | |
| 1058 | + "primaryKey": false, | |
| 1059 | + "notNull": true | |
| 1060 | + }, | |
| 1061 | + "updated_at": { | |
| 1062 | + "name": "updated_at", | |
| 1063 | + "type": "timestamp with time zone", | |
| 1064 | + "primaryKey": false, | |
| 1065 | + "notNull": true, | |
| 1066 | + "default": "now()" | |
| 1067 | + } | |
| 1068 | + }, | |
| 1069 | + "indexes": {}, | |
| 1070 | + "foreignKeys": { | |
| 1071 | + "game_states_user_id_users_id_fk": { | |
| 1072 | + "name": "game_states_user_id_users_id_fk", | |
| 1073 | + "tableFrom": "game_states", | |
| 1074 | + "tableTo": "users", | |
| 1075 | + "columnsFrom": [ | |
| 1076 | + "user_id" | |
| 1077 | + ], | |
| 1078 | + "columnsTo": [ | |
| 1079 | + "id" | |
| 1080 | + ], | |
| 1081 | + "onDelete": "cascade", | |
| 1082 | + "onUpdate": "no action" | |
| 1083 | + }, | |
| 1084 | + "game_states_game_id_games_id_fk": { | |
| 1085 | + "name": "game_states_game_id_games_id_fk", | |
| 1086 | + "tableFrom": "game_states", | |
| 1087 | + "tableTo": "games", | |
| 1088 | + "columnsFrom": [ | |
| 1089 | + "game_id" | |
| 1090 | + ], | |
| 1091 | + "columnsTo": [ | |
| 1092 | + "id" | |
| 1093 | + ], | |
| 1094 | + "onDelete": "cascade", | |
| 1095 | + "onUpdate": "no action" | |
| 1096 | + } | |
| 1097 | + }, | |
| 1098 | + "compositePrimaryKeys": { | |
| 1099 | + "game_states_user_id_game_id_pk": { | |
| 1100 | + "name": "game_states_user_id_game_id_pk", | |
| 1101 | + "columns": [ | |
| 1102 | + "user_id", | |
| 1103 | + "game_id" | |
| 1104 | + ] | |
| 1105 | + } | |
| 1106 | + }, | |
| 1107 | + "uniqueConstraints": {}, | |
| 1108 | + "policies": {}, | |
| 1109 | + "checkConstraints": {}, | |
| 1110 | + "isRLSEnabled": false | |
| 1111 | + }, | |
| 1112 | + "public.game_statistics": { | |
| 1113 | + "name": "game_statistics", | |
| 1114 | + "schema": "", | |
| 1115 | + "columns": { | |
| 1116 | + "game_id": { | |
| 1117 | + "name": "game_id", | |
| 1118 | + "type": "uuid", | |
| 1119 | + "primaryKey": true, | |
| 1120 | + "notNull": true | |
| 1121 | + }, | |
| 1122 | + "launches": { | |
| 1123 | + "name": "launches", | |
| 1124 | + "type": "bigint", | |
| 1125 | + "primaryKey": false, | |
| 1126 | + "notNull": true, | |
| 1127 | + "default": 0 | |
| 1128 | + }, | |
| 1129 | + "spins": { | |
| 1130 | + "name": "spins", | |
| 1131 | + "type": "bigint", | |
| 1132 | + "primaryKey": false, | |
| 1133 | + "notNull": true, | |
| 1134 | + "default": 0 | |
| 1135 | + }, | |
| 1136 | + "wagered": { | |
| 1137 | + "name": "wagered", | |
| 1138 | + "type": "bigint", | |
| 1139 | + "primaryKey": false, | |
| 1140 | + "notNull": true, | |
| 1141 | + "default": 0 | |
| 1142 | + }, | |
| 1143 | + "won": { | |
| 1144 | + "name": "won", | |
| 1145 | + "type": "bigint", | |
| 1146 | + "primaryKey": false, | |
| 1147 | + "notNull": true, | |
| 1148 | + "default": 0 | |
| 1149 | + }, | |
| 1150 | + "wins": { | |
| 1151 | + "name": "wins", | |
| 1152 | + "type": "bigint", | |
| 1153 | + "primaryKey": false, | |
| 1154 | + "notNull": true, | |
| 1155 | + "default": 0 | |
| 1156 | + }, | |
| 1157 | + "bonuses": { | |
| 1158 | + "name": "bonuses", | |
| 1159 | + "type": "bigint", | |
| 1160 | + "primaryKey": false, | |
| 1161 | + "notNull": true, | |
| 1162 | + "default": 0 | |
| 1163 | + }, | |
| 1164 | + "free_spins": { | |
| 1165 | + "name": "free_spins", | |
| 1166 | + "type": "bigint", | |
| 1167 | + "primaryKey": false, | |
| 1168 | + "notNull": true, | |
| 1169 | + "default": 0 | |
| 1170 | + }, | |
| 1171 | + "big_wins": { | |
| 1172 | + "name": "big_wins", | |
| 1173 | + "type": "bigint", | |
| 1174 | + "primaryKey": false, | |
| 1175 | + "notNull": true, | |
| 1176 | + "default": 0 | |
| 1177 | + }, | |
| 1178 | + "max_win": { | |
| 1179 | + "name": "max_win", | |
| 1180 | + "type": "bigint", | |
| 1181 | + "primaryKey": false, | |
| 1182 | + "notNull": true, | |
| 1183 | + "default": 0 | |
| 1184 | + }, | |
| 1185 | + "max_multiplier": { | |
| 1186 | + "name": "max_multiplier", | |
| 1187 | + "type": "numeric(12, 2)", | |
| 1188 | + "primaryKey": false, | |
| 1189 | + "notNull": true, | |
| 1190 | + "default": "'0'" | |
| 1191 | + }, | |
| 1192 | + "favorites": { | |
| 1193 | + "name": "favorites", | |
| 1194 | + "type": "integer", | |
| 1195 | + "primaryKey": false, | |
| 1196 | + "notNull": true, | |
| 1197 | + "default": 0 | |
| 1198 | + }, | |
| 1199 | + "updated_at": { | |
| 1200 | + "name": "updated_at", | |
| 1201 | + "type": "timestamp with time zone", | |
| 1202 | + "primaryKey": false, | |
| 1203 | + "notNull": true, | |
| 1204 | + "default": "now()" | |
| 1205 | + } | |
| 1206 | + }, | |
| 1207 | + "indexes": {}, | |
| 1208 | + "foreignKeys": { | |
| 1209 | + "game_statistics_game_id_games_id_fk": { | |
| 1210 | + "name": "game_statistics_game_id_games_id_fk", | |
| 1211 | + "tableFrom": "game_statistics", | |
| 1212 | + "tableTo": "games", | |
| 1213 | + "columnsFrom": [ | |
| 1214 | + "game_id" | |
| 1215 | + ], | |
| 1216 | + "columnsTo": [ | |
| 1217 | + "id" | |
| 1218 | + ], | |
| 1219 | + "onDelete": "cascade", | |
| 1220 | + "onUpdate": "no action" | |
| 1221 | + } | |
| 1222 | + }, | |
| 1223 | + "compositePrimaryKeys": {}, | |
| 1224 | + "uniqueConstraints": {}, | |
| 1225 | + "policies": {}, | |
| 1226 | + "checkConstraints": {}, | |
| 1227 | + "isRLSEnabled": false | |
| 1228 | + }, | |
| 1229 | + "public.game_versions": { | |
| 1230 | + "name": "game_versions", | |
| 1231 | + "schema": "", | |
| 1232 | + "columns": { | |
| 1233 | + "id": { | |
| 1234 | + "name": "id", | |
| 1235 | + "type": "uuid", | |
| 1236 | + "primaryKey": true, | |
| 1237 | + "notNull": true, | |
| 1238 | + "default": "gen_random_uuid()" | |
| 1239 | + }, | |
| 1240 | + "game_id": { | |
| 1241 | + "name": "game_id", | |
| 1242 | + "type": "uuid", | |
| 1243 | + "primaryKey": false, | |
| 1244 | + "notNull": true | |
| 1245 | + }, | |
| 1246 | + "version": { | |
| 1247 | + "name": "version", | |
| 1248 | + "type": "varchar(16)", | |
| 1249 | + "primaryKey": false, | |
| 1250 | + "notNull": true | |
| 1251 | + }, | |
| 1252 | + "definition": { | |
| 1253 | + "name": "definition", | |
| 1254 | + "type": "jsonb", | |
| 1255 | + "primaryKey": false, | |
| 1256 | + "notNull": true | |
| 1257 | + }, | |
| 1258 | + "definition_hash": { | |
| 1259 | + "name": "definition_hash", | |
| 1260 | + "type": "varchar(64)", | |
| 1261 | + "primaryKey": false, | |
| 1262 | + "notNull": true | |
| 1263 | + }, | |
| 1264 | + "certification": { | |
| 1265 | + "name": "certification", | |
| 1266 | + "type": "jsonb", | |
| 1267 | + "primaryKey": false, | |
| 1268 | + "notNull": false | |
| 1269 | + }, | |
| 1270 | + "status": { | |
| 1271 | + "name": "status", | |
| 1272 | + "type": "varchar(16)", | |
| 1273 | + "primaryKey": false, | |
| 1274 | + "notNull": true, | |
| 1275 | + "default": "'draft'" | |
| 1276 | + }, | |
| 1277 | + "created_at": { | |
| 1278 | + "name": "created_at", | |
| 1279 | + "type": "timestamp with time zone", | |
| 1280 | + "primaryKey": false, | |
| 1281 | + "notNull": true, | |
| 1282 | + "default": "now()" | |
| 1283 | + } | |
| 1284 | + }, | |
| 1285 | + "indexes": { | |
| 1286 | + "game_versions_game_version_idx": { | |
| 1287 | + "name": "game_versions_game_version_idx", | |
| 1288 | + "columns": [ | |
| 1289 | + { | |
| 1290 | + "expression": "game_id", | |
| 1291 | + "isExpression": false, | |
| 1292 | + "asc": true, | |
| 1293 | + "nulls": "last" | |
| 1294 | + }, | |
| 1295 | + { | |
| 1296 | + "expression": "version", | |
| 1297 | + "isExpression": false, | |
| 1298 | + "asc": true, | |
| 1299 | + "nulls": "last" | |
| 1300 | + } | |
| 1301 | + ], | |
| 1302 | + "isUnique": true, | |
| 1303 | + "concurrently": false, | |
| 1304 | + "method": "btree", | |
| 1305 | + "with": {} | |
| 1306 | + } | |
| 1307 | + }, | |
| 1308 | + "foreignKeys": { | |
| 1309 | + "game_versions_game_id_games_id_fk": { | |
| 1310 | + "name": "game_versions_game_id_games_id_fk", | |
| 1311 | + "tableFrom": "game_versions", | |
| 1312 | + "tableTo": "games", | |
| 1313 | + "columnsFrom": [ | |
| 1314 | + "game_id" | |
| 1315 | + ], | |
| 1316 | + "columnsTo": [ | |
| 1317 | + "id" | |
| 1318 | + ], | |
| 1319 | + "onDelete": "cascade", | |
| 1320 | + "onUpdate": "no action" | |
| 1321 | + } | |
| 1322 | + }, | |
| 1323 | + "compositePrimaryKeys": {}, | |
| 1324 | + "uniqueConstraints": {}, | |
| 1325 | + "policies": {}, | |
| 1326 | + "checkConstraints": {}, | |
| 1327 | + "isRLSEnabled": false | |
| 1328 | + }, | |
| 1329 | + "public.games": { | |
| 1330 | + "name": "games", | |
| 1331 | + "schema": "", | |
| 1332 | + "columns": { | |
| 1333 | + "id": { | |
| 1334 | + "name": "id", | |
| 1335 | + "type": "uuid", | |
| 1336 | + "primaryKey": true, | |
| 1337 | + "notNull": true, | |
| 1338 | + "default": "gen_random_uuid()" | |
| 1339 | + }, | |
| 1340 | + "slug": { | |
| 1341 | + "name": "slug", | |
| 1342 | + "type": "varchar(64)", | |
| 1343 | + "primaryKey": false, | |
| 1344 | + "notNull": true | |
| 1345 | + }, | |
| 1346 | + "name": { | |
| 1347 | + "name": "name", | |
| 1348 | + "type": "varchar(80)", | |
| 1349 | + "primaryKey": false, | |
| 1350 | + "notNull": true | |
| 1351 | + }, | |
| 1352 | + "version": { | |
| 1353 | + "name": "version", | |
| 1354 | + "type": "varchar(16)", | |
| 1355 | + "primaryKey": false, | |
| 1356 | + "notNull": true | |
| 1357 | + }, | |
| 1358 | + "lifecycle": { | |
| 1359 | + "name": "lifecycle", | |
| 1360 | + "type": "varchar(16)", | |
| 1361 | + "primaryKey": false, | |
| 1362 | + "notNull": true, | |
| 1363 | + "default": "'draft'" | |
| 1364 | + }, | |
| 1365 | + "sort_order": { | |
| 1366 | + "name": "sort_order", | |
| 1367 | + "type": "integer", | |
| 1368 | + "primaryKey": false, | |
| 1369 | + "notNull": true, | |
| 1370 | + "default": 0 | |
| 1371 | + }, | |
| 1372 | + "is_featured": { | |
| 1373 | + "name": "is_featured", | |
| 1374 | + "type": "boolean", | |
| 1375 | + "primaryKey": false, | |
| 1376 | + "notNull": true, | |
| 1377 | + "default": false | |
| 1378 | + }, | |
| 1379 | + "is_new": { | |
| 1380 | + "name": "is_new", | |
| 1381 | + "type": "boolean", | |
| 1382 | + "primaryKey": false, | |
| 1383 | + "notNull": true, | |
| 1384 | + "default": true | |
| 1385 | + }, | |
| 1386 | + "summary": { | |
| 1387 | + "name": "summary", | |
| 1388 | + "type": "jsonb", | |
| 1389 | + "primaryKey": false, | |
| 1390 | + "notNull": true | |
| 1391 | + }, | |
| 1392 | + "created_at": { | |
| 1393 | + "name": "created_at", | |
| 1394 | + "type": "timestamp with time zone", | |
| 1395 | + "primaryKey": false, | |
| 1396 | + "notNull": true, | |
| 1397 | + "default": "now()" | |
| 1398 | + }, | |
| 1399 | + "published_at": { | |
| 1400 | + "name": "published_at", | |
| 1401 | + "type": "timestamp with time zone", | |
| 1402 | + "primaryKey": false, | |
| 1403 | + "notNull": false | |
| 1404 | + }, | |
| 1405 | + "updated_at": { | |
| 1406 | + "name": "updated_at", | |
| 1407 | + "type": "timestamp with time zone", | |
| 1408 | + "primaryKey": false, | |
| 1409 | + "notNull": true, | |
| 1410 | + "default": "now()" | |
| 1411 | + } | |
| 1412 | + }, | |
| 1413 | + "indexes": { | |
| 1414 | + "games_slug_idx": { | |
| 1415 | + "name": "games_slug_idx", | |
| 1416 | + "columns": [ | |
| 1417 | + { | |
| 1418 | + "expression": "slug", | |
| 1419 | + "isExpression": false, | |
| 1420 | + "asc": true, | |
| 1421 | + "nulls": "last" | |
| 1422 | + } | |
| 1423 | + ], | |
| 1424 | + "isUnique": true, | |
| 1425 | + "concurrently": false, | |
| 1426 | + "method": "btree", | |
| 1427 | + "with": {} | |
| 1428 | + } | |
| 1429 | + }, | |
| 1430 | + "foreignKeys": {}, | |
| 1431 | + "compositePrimaryKeys": {}, | |
| 1432 | + "uniqueConstraints": {}, | |
| 1433 | + "policies": {}, | |
| 1434 | + "checkConstraints": {}, | |
| 1435 | + "isRLSEnabled": false | |
| 1436 | + }, | |
| 1437 | + "public.leaderboards": { | |
| 1438 | + "name": "leaderboards", | |
| 1439 | + "schema": "", | |
| 1440 | + "columns": { | |
| 1441 | + "id": { | |
| 1442 | + "name": "id", | |
| 1443 | + "type": "uuid", | |
| 1444 | + "primaryKey": true, | |
| 1445 | + "notNull": true, | |
| 1446 | + "default": "gen_random_uuid()" | |
| 1447 | + }, | |
| 1448 | + "category": { | |
| 1449 | + "name": "category", | |
| 1450 | + "type": "varchar(32)", | |
| 1451 | + "primaryKey": false, | |
| 1452 | + "notNull": true | |
| 1453 | + }, | |
| 1454 | + "period_key": { | |
| 1455 | + "name": "period_key", | |
| 1456 | + "type": "varchar(16)", | |
| 1457 | + "primaryKey": false, | |
| 1458 | + "notNull": true | |
| 1459 | + }, | |
| 1460 | + "user_id": { | |
| 1461 | + "name": "user_id", | |
| 1462 | + "type": "uuid", | |
| 1463 | + "primaryKey": false, | |
| 1464 | + "notNull": true | |
| 1465 | + }, | |
| 1466 | + "value": { | |
| 1467 | + "name": "value", | |
| 1468 | + "type": "numeric(18, 2)", | |
| 1469 | + "primaryKey": false, | |
| 1470 | + "notNull": true | |
| 1471 | + }, | |
| 1472 | + "game_slug": { | |
| 1473 | + "name": "game_slug", | |
| 1474 | + "type": "varchar(64)", | |
| 1475 | + "primaryKey": false, | |
| 1476 | + "notNull": false | |
| 1477 | + }, | |
| 1478 | + "round_id": { | |
| 1479 | + "name": "round_id", | |
| 1480 | + "type": "varchar(32)", | |
| 1481 | + "primaryKey": false, | |
| 1482 | + "notNull": false | |
| 1483 | + }, | |
| 1484 | + "updated_at": { | |
| 1485 | + "name": "updated_at", | |
| 1486 | + "type": "timestamp with time zone", | |
| 1487 | + "primaryKey": false, | |
| 1488 | + "notNull": true, | |
| 1489 | + "default": "now()" | |
| 1490 | + } | |
| 1491 | + }, | |
| 1492 | + "indexes": { | |
| 1493 | + "leaderboards_unique_idx": { | |
| 1494 | + "name": "leaderboards_unique_idx", | |
| 1495 | + "columns": [ | |
| 1496 | + { | |
| 1497 | + "expression": "category", | |
| 1498 | + "isExpression": false, | |
| 1499 | + "asc": true, | |
| 1500 | + "nulls": "last" | |
| 1501 | + }, | |
| 1502 | + { | |
| 1503 | + "expression": "period_key", | |
| 1504 | + "isExpression": false, | |
| 1505 | + "asc": true, | |
| 1506 | + "nulls": "last" | |
| 1507 | + }, | |
| 1508 | + { | |
| 1509 | + "expression": "user_id", | |
| 1510 | + "isExpression": false, | |
| 1511 | + "asc": true, | |
| 1512 | + "nulls": "last" | |
| 1513 | + } | |
| 1514 | + ], | |
| 1515 | + "isUnique": true, | |
| 1516 | + "concurrently": false, | |
| 1517 | + "method": "btree", | |
| 1518 | + "with": {} | |
| 1519 | + }, | |
| 1520 | + "leaderboards_rank_idx": { | |
| 1521 | + "name": "leaderboards_rank_idx", | |
| 1522 | + "columns": [ | |
| 1523 | + { | |
| 1524 | + "expression": "category", | |
| 1525 | + "isExpression": false, | |
| 1526 | + "asc": true, | |
| 1527 | + "nulls": "last" | |
| 1528 | + }, | |
| 1529 | + { | |
| 1530 | + "expression": "period_key", | |
| 1531 | + "isExpression": false, | |
| 1532 | + "asc": true, | |
| 1533 | + "nulls": "last" | |
| 1534 | + }, | |
| 1535 | + { | |
| 1536 | + "expression": "value", | |
| 1537 | + "isExpression": false, | |
| 1538 | + "asc": true, | |
| 1539 | + "nulls": "last" | |
| 1540 | + } | |
| 1541 | + ], | |
| 1542 | + "isUnique": false, | |
| 1543 | + "concurrently": false, | |
| 1544 | + "method": "btree", | |
| 1545 | + "with": {} | |
| 1546 | + } | |
| 1547 | + }, | |
| 1548 | + "foreignKeys": { | |
| 1549 | + "leaderboards_user_id_users_id_fk": { | |
| 1550 | + "name": "leaderboards_user_id_users_id_fk", | |
| 1551 | + "tableFrom": "leaderboards", | |
| 1552 | + "tableTo": "users", | |
| 1553 | + "columnsFrom": [ | |
| 1554 | + "user_id" | |
| 1555 | + ], | |
| 1556 | + "columnsTo": [ | |
| 1557 | + "id" | |
| 1558 | + ], | |
| 1559 | + "onDelete": "cascade", | |
| 1560 | + "onUpdate": "no action" | |
| 1561 | + } | |
| 1562 | + }, | |
| 1563 | + "compositePrimaryKeys": {}, | |
| 1564 | + "uniqueConstraints": {}, | |
| 1565 | + "policies": {}, | |
| 1566 | + "checkConstraints": {}, | |
| 1567 | + "isRLSEnabled": false | |
| 1568 | + }, | |
| 1569 | + "public.missions": { | |
| 1570 | + "name": "missions", | |
| 1571 | + "schema": "", | |
| 1572 | + "columns": { | |
| 1573 | + "key": { | |
| 1574 | + "name": "key", | |
| 1575 | + "type": "varchar(48)", | |
| 1576 | + "primaryKey": true, | |
| 1577 | + "notNull": true | |
| 1578 | + }, | |
| 1579 | + "name": { | |
| 1580 | + "name": "name", | |
| 1581 | + "type": "varchar(80)", | |
| 1582 | + "primaryKey": false, | |
| 1583 | + "notNull": true | |
| 1584 | + }, | |
| 1585 | + "description": { | |
| 1586 | + "name": "description", | |
| 1587 | + "type": "text", | |
| 1588 | + "primaryKey": false, | |
| 1589 | + "notNull": true | |
| 1590 | + }, | |
| 1591 | + "period": { | |
| 1592 | + "name": "period", | |
| 1593 | + "type": "varchar(8)", | |
| 1594 | + "primaryKey": false, | |
| 1595 | + "notNull": true | |
| 1596 | + }, | |
| 1597 | + "metric": { | |
| 1598 | + "name": "metric", | |
| 1599 | + "type": "varchar(32)", | |
| 1600 | + "primaryKey": false, | |
| 1601 | + "notNull": true | |
| 1602 | + }, | |
| 1603 | + "target": { | |
| 1604 | + "name": "target", | |
| 1605 | + "type": "bigint", | |
| 1606 | + "primaryKey": false, | |
| 1607 | + "notNull": true | |
| 1608 | + }, | |
| 1609 | + "reward_credits": { | |
| 1610 | + "name": "reward_credits", | |
| 1611 | + "type": "integer", | |
| 1612 | + "primaryKey": false, | |
| 1613 | + "notNull": true, | |
| 1614 | + "default": 0 | |
| 1615 | + }, | |
| 1616 | + "reward_xp": { | |
| 1617 | + "name": "reward_xp", | |
| 1618 | + "type": "integer", | |
| 1619 | + "primaryKey": false, | |
| 1620 | + "notNull": true, | |
| 1621 | + "default": 0 | |
| 1622 | + }, | |
| 1623 | + "enabled": { | |
| 1624 | + "name": "enabled", | |
| 1625 | + "type": "boolean", | |
| 1626 | + "primaryKey": false, | |
| 1627 | + "notNull": true, | |
| 1628 | + "default": true | |
| 1629 | + }, | |
| 1630 | + "sort_order": { | |
| 1631 | + "name": "sort_order", | |
| 1632 | + "type": "integer", | |
| 1633 | + "primaryKey": false, | |
| 1634 | + "notNull": true, | |
| 1635 | + "default": 0 | |
| 1636 | + } | |
| 1637 | + }, | |
| 1638 | + "indexes": {}, | |
| 1639 | + "foreignKeys": {}, | |
| 1640 | + "compositePrimaryKeys": {}, | |
| 1641 | + "uniqueConstraints": {}, | |
| 1642 | + "policies": {}, | |
| 1643 | + "checkConstraints": {}, | |
| 1644 | + "isRLSEnabled": false | |
| 1645 | + }, | |
| 1646 | + "public.platform_settings": { | |
| 1647 | + "name": "platform_settings", | |
| 1648 | + "schema": "", | |
| 1649 | + "columns": { | |
| 1650 | + "key": { | |
| 1651 | + "name": "key", | |
| 1652 | + "type": "varchar(64)", | |
| 1653 | + "primaryKey": true, | |
| 1654 | + "notNull": true | |
| 1655 | + }, | |
| 1656 | + "value": { | |
| 1657 | + "name": "value", | |
| 1658 | + "type": "jsonb", | |
| 1659 | + "primaryKey": false, | |
| 1660 | + "notNull": true | |
| 1661 | + }, | |
| 1662 | + "updated_at": { | |
| 1663 | + "name": "updated_at", | |
| 1664 | + "type": "timestamp with time zone", | |
| 1665 | + "primaryKey": false, | |
| 1666 | + "notNull": true, | |
| 1667 | + "default": "now()" | |
| 1668 | + } | |
| 1669 | + }, | |
| 1670 | + "indexes": {}, | |
| 1671 | + "foreignKeys": {}, | |
| 1672 | + "compositePrimaryKeys": {}, | |
| 1673 | + "uniqueConstraints": {}, | |
| 1674 | + "policies": {}, | |
| 1675 | + "checkConstraints": {}, | |
| 1676 | + "isRLSEnabled": false | |
| 1677 | + }, | |
| 1678 | + "public.player_levels": { | |
| 1679 | + "name": "player_levels", | |
| 1680 | + "schema": "", | |
| 1681 | + "columns": { | |
| 1682 | + "level": { | |
| 1683 | + "name": "level", | |
| 1684 | + "type": "integer", | |
| 1685 | + "primaryKey": true, | |
| 1686 | + "notNull": true | |
| 1687 | + }, | |
| 1688 | + "xp_required": { | |
| 1689 | + "name": "xp_required", | |
| 1690 | + "type": "bigint", | |
| 1691 | + "primaryKey": false, | |
| 1692 | + "notNull": true | |
| 1693 | + }, | |
| 1694 | + "xp_cumulative": { | |
| 1695 | + "name": "xp_cumulative", | |
| 1696 | + "type": "bigint", | |
| 1697 | + "primaryKey": false, | |
| 1698 | + "notNull": true | |
| 1699 | + }, | |
| 1700 | + "reward_credits": { | |
| 1701 | + "name": "reward_credits", | |
| 1702 | + "type": "integer", | |
| 1703 | + "primaryKey": false, | |
| 1704 | + "notNull": true | |
| 1705 | + }, | |
| 1706 | + "title": { | |
| 1707 | + "name": "title", | |
| 1708 | + "type": "varchar(48)", | |
| 1709 | + "primaryKey": false, | |
| 1710 | + "notNull": true | |
| 1711 | + } | |
| 1712 | + }, | |
| 1713 | + "indexes": {}, | |
| 1714 | + "foreignKeys": {}, | |
| 1715 | + "compositePrimaryKeys": {}, | |
| 1716 | + "uniqueConstraints": {}, | |
| 1717 | + "policies": {}, | |
| 1718 | + "checkConstraints": {}, | |
| 1719 | + "isRLSEnabled": false | |
| 1720 | + }, | |
| 1721 | + "public.recovery_codes": { | |
| 1722 | + "name": "recovery_codes", | |
| 1723 | + "schema": "", | |
| 1724 | + "columns": { | |
| 1725 | + "user_id": { | |
| 1726 | + "name": "user_id", | |
| 1727 | + "type": "uuid", | |
| 1728 | + "primaryKey": true, | |
| 1729 | + "notNull": true | |
| 1730 | + }, | |
| 1731 | + "code_hash": { | |
| 1732 | + "name": "code_hash", | |
| 1733 | + "type": "text", | |
| 1734 | + "primaryKey": false, | |
| 1735 | + "notNull": true | |
| 1736 | + }, | |
| 1737 | + "created_at": { | |
| 1738 | + "name": "created_at", | |
| 1739 | + "type": "timestamp with time zone", | |
| 1740 | + "primaryKey": false, | |
| 1741 | + "notNull": true, | |
| 1742 | + "default": "now()" | |
| 1743 | + }, | |
| 1744 | + "rotated_at": { | |
| 1745 | + "name": "rotated_at", | |
| 1746 | + "type": "timestamp with time zone", | |
| 1747 | + "primaryKey": false, | |
| 1748 | + "notNull": false | |
| 1749 | + }, | |
| 1750 | + "used_at": { | |
| 1751 | + "name": "used_at", | |
| 1752 | + "type": "timestamp with time zone", | |
| 1753 | + "primaryKey": false, | |
| 1754 | + "notNull": false | |
| 1755 | + }, | |
| 1756 | + "use_count": { | |
| 1757 | + "name": "use_count", | |
| 1758 | + "type": "integer", | |
| 1759 | + "primaryKey": false, | |
| 1760 | + "notNull": true, | |
| 1761 | + "default": 0 | |
| 1762 | + } | |
| 1763 | + }, | |
| 1764 | + "indexes": {}, | |
| 1765 | + "foreignKeys": { | |
| 1766 | + "recovery_codes_user_id_users_id_fk": { | |
| 1767 | + "name": "recovery_codes_user_id_users_id_fk", | |
| 1768 | + "tableFrom": "recovery_codes", | |
| 1769 | + "tableTo": "users", | |
| 1770 | + "columnsFrom": [ | |
| 1771 | + "user_id" | |
| 1772 | + ], | |
| 1773 | + "columnsTo": [ | |
| 1774 | + "id" | |
| 1775 | + ], | |
| 1776 | + "onDelete": "cascade", | |
| 1777 | + "onUpdate": "no action" | |
| 1778 | + } | |
| 1779 | + }, | |
| 1780 | + "compositePrimaryKeys": {}, | |
| 1781 | + "uniqueConstraints": {}, | |
| 1782 | + "policies": {}, | |
| 1783 | + "checkConstraints": {}, | |
| 1784 | + "isRLSEnabled": false | |
| 1785 | + }, | |
| 1786 | + "public.security_events": { | |
| 1787 | + "name": "security_events", | |
| 1788 | + "schema": "", | |
| 1789 | + "columns": { | |
| 1790 | + "id": { | |
| 1791 | + "name": "id", | |
| 1792 | + "type": "uuid", | |
| 1793 | + "primaryKey": true, | |
| 1794 | + "notNull": true, | |
| 1795 | + "default": "gen_random_uuid()" | |
| 1796 | + }, | |
| 1797 | + "user_id": { | |
| 1798 | + "name": "user_id", | |
| 1799 | + "type": "uuid", | |
| 1800 | + "primaryKey": false, | |
| 1801 | + "notNull": false | |
| 1802 | + }, | |
| 1803 | + "admin_id": { | |
| 1804 | + "name": "admin_id", | |
| 1805 | + "type": "uuid", | |
| 1806 | + "primaryKey": false, | |
| 1807 | + "notNull": false | |
| 1808 | + }, | |
| 1809 | + "type": { | |
| 1810 | + "name": "type", | |
| 1811 | + "type": "varchar(48)", | |
| 1812 | + "primaryKey": false, | |
| 1813 | + "notNull": true | |
| 1814 | + }, | |
| 1815 | + "severity": { | |
| 1816 | + "name": "severity", | |
| 1817 | + "type": "varchar(8)", | |
| 1818 | + "primaryKey": false, | |
| 1819 | + "notNull": true, | |
| 1820 | + "default": "'info'" | |
| 1821 | + }, | |
| 1822 | + "ip": { | |
| 1823 | + "name": "ip", | |
| 1824 | + "type": "varchar(64)", | |
| 1825 | + "primaryKey": false, | |
| 1826 | + "notNull": false | |
| 1827 | + }, | |
| 1828 | + "user_agent": { | |
| 1829 | + "name": "user_agent", | |
| 1830 | + "type": "text", | |
| 1831 | + "primaryKey": false, | |
| 1832 | + "notNull": false | |
| 1833 | + }, | |
| 1834 | + "meta": { | |
| 1835 | + "name": "meta", | |
| 1836 | + "type": "jsonb", | |
| 1837 | + "primaryKey": false, | |
| 1838 | + "notNull": false | |
| 1839 | + }, | |
| 1840 | + "created_at": { | |
| 1841 | + "name": "created_at", | |
| 1842 | + "type": "timestamp with time zone", | |
| 1843 | + "primaryKey": false, | |
| 1844 | + "notNull": true, | |
| 1845 | + "default": "now()" | |
| 1846 | + } | |
| 1847 | + }, | |
| 1848 | + "indexes": { | |
| 1849 | + "security_events_created_idx": { | |
| 1850 | + "name": "security_events_created_idx", | |
| 1851 | + "columns": [ | |
| 1852 | + { | |
| 1853 | + "expression": "created_at", | |
| 1854 | + "isExpression": false, | |
| 1855 | + "asc": true, | |
| 1856 | + "nulls": "last" | |
| 1857 | + } | |
| 1858 | + ], | |
| 1859 | + "isUnique": false, | |
| 1860 | + "concurrently": false, | |
| 1861 | + "method": "btree", | |
| 1862 | + "with": {} | |
| 1863 | + }, | |
| 1864 | + "security_events_type_idx": { | |
| 1865 | + "name": "security_events_type_idx", | |
| 1866 | + "columns": [ | |
| 1867 | + { | |
| 1868 | + "expression": "type", | |
| 1869 | + "isExpression": false, | |
| 1870 | + "asc": true, | |
| 1871 | + "nulls": "last" | |
| 1872 | + } | |
| 1873 | + ], | |
| 1874 | + "isUnique": false, | |
| 1875 | + "concurrently": false, | |
| 1876 | + "method": "btree", | |
| 1877 | + "with": {} | |
| 1878 | + }, | |
| 1879 | + "security_events_user_idx": { | |
| 1880 | + "name": "security_events_user_idx", | |
| 1881 | + "columns": [ | |
| 1882 | + { | |
| 1883 | + "expression": "user_id", | |
| 1884 | + "isExpression": false, | |
| 1885 | + "asc": true, | |
| 1886 | + "nulls": "last" | |
| 1887 | + } | |
| 1888 | + ], | |
| 1889 | + "isUnique": false, | |
| 1890 | + "concurrently": false, | |
| 1891 | + "method": "btree", | |
| 1892 | + "with": {} | |
| 1893 | + } | |
| 1894 | + }, | |
| 1895 | + "foreignKeys": { | |
| 1896 | + "security_events_user_id_users_id_fk": { | |
| 1897 | + "name": "security_events_user_id_users_id_fk", | |
| 1898 | + "tableFrom": "security_events", | |
| 1899 | + "tableTo": "users", | |
| 1900 | + "columnsFrom": [ | |
| 1901 | + "user_id" | |
| 1902 | + ], | |
| 1903 | + "columnsTo": [ | |
| 1904 | + "id" | |
| 1905 | + ], | |
| 1906 | + "onDelete": "set null", | |
| 1907 | + "onUpdate": "no action" | |
| 1908 | + } | |
| 1909 | + }, | |
| 1910 | + "compositePrimaryKeys": {}, | |
| 1911 | + "uniqueConstraints": {}, | |
| 1912 | + "policies": {}, | |
| 1913 | + "checkConstraints": {}, | |
| 1914 | + "isRLSEnabled": false | |
| 1915 | + }, | |
| 1916 | + "public.sessions": { | |
| 1917 | + "name": "sessions", | |
| 1918 | + "schema": "", | |
| 1919 | + "columns": { | |
| 1920 | + "id": { | |
| 1921 | + "name": "id", | |
| 1922 | + "type": "uuid", | |
| 1923 | + "primaryKey": true, | |
| 1924 | + "notNull": true, | |
| 1925 | + "default": "gen_random_uuid()" | |
| 1926 | + }, | |
| 1927 | + "user_id": { | |
| 1928 | + "name": "user_id", | |
| 1929 | + "type": "uuid", | |
| 1930 | + "primaryKey": false, | |
| 1931 | + "notNull": true | |
| 1932 | + }, | |
| 1933 | + "token_hash": { | |
| 1934 | + "name": "token_hash", | |
| 1935 | + "type": "text", | |
| 1936 | + "primaryKey": false, | |
| 1937 | + "notNull": true | |
| 1938 | + }, | |
| 1939 | + "created_at": { | |
| 1940 | + "name": "created_at", | |
| 1941 | + "type": "timestamp with time zone", | |
| 1942 | + "primaryKey": false, | |
| 1943 | + "notNull": true, | |
| 1944 | + "default": "now()" | |
| 1945 | + }, | |
| 1946 | + "expires_at": { | |
| 1947 | + "name": "expires_at", | |
| 1948 | + "type": "timestamp with time zone", | |
| 1949 | + "primaryKey": false, | |
| 1950 | + "notNull": true | |
| 1951 | + }, | |
| 1952 | + "last_seen_at": { | |
| 1953 | + "name": "last_seen_at", | |
| 1954 | + "type": "timestamp with time zone", | |
| 1955 | + "primaryKey": false, | |
| 1956 | + "notNull": true, | |
| 1957 | + "default": "now()" | |
| 1958 | + }, | |
| 1959 | + "user_agent": { | |
| 1960 | + "name": "user_agent", | |
| 1961 | + "type": "text", | |
| 1962 | + "primaryKey": false, | |
| 1963 | + "notNull": false | |
| 1964 | + }, | |
| 1965 | + "ip": { | |
| 1966 | + "name": "ip", | |
| 1967 | + "type": "varchar(64)", | |
| 1968 | + "primaryKey": false, | |
| 1969 | + "notNull": false | |
| 1970 | + } | |
| 1971 | + }, | |
| 1972 | + "indexes": { | |
| 1973 | + "sessions_token_hash_idx": { | |
| 1974 | + "name": "sessions_token_hash_idx", | |
| 1975 | + "columns": [ | |
| 1976 | + { | |
| 1977 | + "expression": "token_hash", | |
| 1978 | + "isExpression": false, | |
| 1979 | + "asc": true, | |
| 1980 | + "nulls": "last" | |
| 1981 | + } | |
| 1982 | + ], | |
| 1983 | + "isUnique": true, | |
| 1984 | + "concurrently": false, | |
| 1985 | + "method": "btree", | |
| 1986 | + "with": {} | |
| 1987 | + }, | |
| 1988 | + "sessions_user_idx": { | |
| 1989 | + "name": "sessions_user_idx", | |
| 1990 | + "columns": [ | |
| 1991 | + { | |
| 1992 | + "expression": "user_id", | |
| 1993 | + "isExpression": false, | |
| 1994 | + "asc": true, | |
| 1995 | + "nulls": "last" | |
| 1996 | + } | |
| 1997 | + ], | |
| 1998 | + "isUnique": false, | |
| 1999 | + "concurrently": false, | |
| 2000 | + "method": "btree", | |
| 2001 | + "with": {} | |
| 2002 | + } | |
| 2003 | + }, | |
| 2004 | + "foreignKeys": { | |
| 2005 | + "sessions_user_id_users_id_fk": { | |
| 2006 | + "name": "sessions_user_id_users_id_fk", | |
| 2007 | + "tableFrom": "sessions", | |
| 2008 | + "tableTo": "users", | |
| 2009 | + "columnsFrom": [ | |
| 2010 | + "user_id" | |
| 2011 | + ], | |
| 2012 | + "columnsTo": [ | |
| 2013 | + "id" | |
| 2014 | + ], | |
| 2015 | + "onDelete": "cascade", | |
| 2016 | + "onUpdate": "no action" | |
| 2017 | + } | |
| 2018 | + }, | |
| 2019 | + "compositePrimaryKeys": {}, | |
| 2020 | + "uniqueConstraints": {}, | |
| 2021 | + "policies": {}, | |
| 2022 | + "checkConstraints": {}, | |
| 2023 | + "isRLSEnabled": false | |
| 2024 | + }, | |
| 2025 | + "public.simulation_runs": { | |
| 2026 | + "name": "simulation_runs", | |
| 2027 | + "schema": "", | |
| 2028 | + "columns": { | |
| 2029 | + "id": { | |
| 2030 | + "name": "id", | |
| 2031 | + "type": "uuid", | |
| 2032 | + "primaryKey": true, | |
| 2033 | + "notNull": true, | |
| 2034 | + "default": "gen_random_uuid()" | |
| 2035 | + }, | |
| 2036 | + "game_slug": { | |
| 2037 | + "name": "game_slug", | |
| 2038 | + "type": "varchar(64)", | |
| 2039 | + "primaryKey": false, | |
| 2040 | + "notNull": true | |
| 2041 | + }, | |
| 2042 | + "game_version": { | |
| 2043 | + "name": "game_version", | |
| 2044 | + "type": "varchar(16)", | |
| 2045 | + "primaryKey": false, | |
| 2046 | + "notNull": true | |
| 2047 | + }, | |
| 2048 | + "spins": { | |
| 2049 | + "name": "spins", | |
| 2050 | + "type": "bigint", | |
| 2051 | + "primaryKey": false, | |
| 2052 | + "notNull": true | |
| 2053 | + }, | |
| 2054 | + "status": { | |
| 2055 | + "name": "status", | |
| 2056 | + "type": "varchar(16)", | |
| 2057 | + "primaryKey": false, | |
| 2058 | + "notNull": true, | |
| 2059 | + "default": "'running'" | |
| 2060 | + }, | |
| 2061 | + "progress": { | |
| 2062 | + "name": "progress", | |
| 2063 | + "type": "bigint", | |
| 2064 | + "primaryKey": false, | |
| 2065 | + "notNull": true, | |
| 2066 | + "default": 0 | |
| 2067 | + }, | |
| 2068 | + "result": { | |
| 2069 | + "name": "result", | |
| 2070 | + "type": "jsonb", | |
| 2071 | + "primaryKey": false, | |
| 2072 | + "notNull": false | |
| 2073 | + }, | |
| 2074 | + "error": { | |
| 2075 | + "name": "error", | |
| 2076 | + "type": "text", | |
| 2077 | + "primaryKey": false, | |
| 2078 | + "notNull": false | |
| 2079 | + }, | |
| 2080 | + "requested_by": { | |
| 2081 | + "name": "requested_by", | |
| 2082 | + "type": "uuid", | |
| 2083 | + "primaryKey": false, | |
| 2084 | + "notNull": false | |
| 2085 | + }, | |
| 2086 | + "created_at": { | |
| 2087 | + "name": "created_at", | |
| 2088 | + "type": "timestamp with time zone", | |
| 2089 | + "primaryKey": false, | |
| 2090 | + "notNull": true, | |
| 2091 | + "default": "now()" | |
| 2092 | + }, | |
| 2093 | + "finished_at": { | |
| 2094 | + "name": "finished_at", | |
| 2095 | + "type": "timestamp with time zone", | |
| 2096 | + "primaryKey": false, | |
| 2097 | + "notNull": false | |
| 2098 | + } | |
| 2099 | + }, | |
| 2100 | + "indexes": { | |
| 2101 | + "simulation_runs_game_idx": { | |
| 2102 | + "name": "simulation_runs_game_idx", | |
| 2103 | + "columns": [ | |
| 2104 | + { | |
| 2105 | + "expression": "game_slug", | |
| 2106 | + "isExpression": false, | |
| 2107 | + "asc": true, | |
| 2108 | + "nulls": "last" | |
| 2109 | + }, | |
| 2110 | + { | |
| 2111 | + "expression": "created_at", | |
| 2112 | + "isExpression": false, | |
| 2113 | + "asc": true, | |
| 2114 | + "nulls": "last" | |
| 2115 | + } | |
| 2116 | + ], | |
| 2117 | + "isUnique": false, | |
| 2118 | + "concurrently": false, | |
| 2119 | + "method": "btree", | |
| 2120 | + "with": {} | |
| 2121 | + } | |
| 2122 | + }, | |
| 2123 | + "foreignKeys": {}, | |
| 2124 | + "compositePrimaryKeys": {}, | |
| 2125 | + "uniqueConstraints": {}, | |
| 2126 | + "policies": {}, | |
| 2127 | + "checkConstraints": {}, | |
| 2128 | + "isRLSEnabled": false | |
| 2129 | + }, | |
| 2130 | + "public.user_achievements": { | |
| 2131 | + "name": "user_achievements", | |
| 2132 | + "schema": "", | |
| 2133 | + "columns": { | |
| 2134 | + "user_id": { | |
| 2135 | + "name": "user_id", | |
| 2136 | + "type": "uuid", | |
| 2137 | + "primaryKey": false, | |
| 2138 | + "notNull": true | |
| 2139 | + }, | |
| 2140 | + "achievement_key": { | |
| 2141 | + "name": "achievement_key", | |
| 2142 | + "type": "varchar(48)", | |
| 2143 | + "primaryKey": false, | |
| 2144 | + "notNull": true | |
| 2145 | + }, | |
| 2146 | + "unlocked_at": { | |
| 2147 | + "name": "unlocked_at", | |
| 2148 | + "type": "timestamp with time zone", | |
| 2149 | + "primaryKey": false, | |
| 2150 | + "notNull": true, | |
| 2151 | + "default": "now()" | |
| 2152 | + } | |
| 2153 | + }, | |
| 2154 | + "indexes": {}, | |
| 2155 | + "foreignKeys": { | |
| 2156 | + "user_achievements_user_id_users_id_fk": { | |
| 2157 | + "name": "user_achievements_user_id_users_id_fk", | |
| 2158 | + "tableFrom": "user_achievements", | |
| 2159 | + "tableTo": "users", | |
| 2160 | + "columnsFrom": [ | |
| 2161 | + "user_id" | |
| 2162 | + ], | |
| 2163 | + "columnsTo": [ | |
| 2164 | + "id" | |
| 2165 | + ], | |
| 2166 | + "onDelete": "cascade", | |
| 2167 | + "onUpdate": "no action" | |
| 2168 | + }, | |
| 2169 | + "user_achievements_achievement_key_achievements_key_fk": { | |
| 2170 | + "name": "user_achievements_achievement_key_achievements_key_fk", | |
| 2171 | + "tableFrom": "user_achievements", | |
| 2172 | + "tableTo": "achievements", | |
| 2173 | + "columnsFrom": [ | |
| 2174 | + "achievement_key" | |
| 2175 | + ], | |
| 2176 | + "columnsTo": [ | |
| 2177 | + "key" | |
| 2178 | + ], | |
| 2179 | + "onDelete": "cascade", | |
| 2180 | + "onUpdate": "no action" | |
| 2181 | + } | |
| 2182 | + }, | |
| 2183 | + "compositePrimaryKeys": { | |
| 2184 | + "user_achievements_user_id_achievement_key_pk": { | |
| 2185 | + "name": "user_achievements_user_id_achievement_key_pk", | |
| 2186 | + "columns": [ | |
| 2187 | + "user_id", | |
| 2188 | + "achievement_key" | |
| 2189 | + ] | |
| 2190 | + } | |
| 2191 | + }, | |
| 2192 | + "uniqueConstraints": {}, | |
| 2193 | + "policies": {}, | |
| 2194 | + "checkConstraints": {}, | |
| 2195 | + "isRLSEnabled": false | |
| 2196 | + }, | |
| 2197 | + "public.user_game_stats": { | |
| 2198 | + "name": "user_game_stats", | |
| 2199 | + "schema": "", | |
| 2200 | + "columns": { | |
| 2201 | + "user_id": { | |
| 2202 | + "name": "user_id", | |
| 2203 | + "type": "uuid", | |
| 2204 | + "primaryKey": false, | |
| 2205 | + "notNull": true | |
| 2206 | + }, | |
| 2207 | + "game_id": { | |
| 2208 | + "name": "game_id", | |
| 2209 | + "type": "uuid", | |
| 2210 | + "primaryKey": false, | |
| 2211 | + "notNull": true | |
| 2212 | + }, | |
| 2213 | + "spins": { | |
| 2214 | + "name": "spins", | |
| 2215 | + "type": "bigint", | |
| 2216 | + "primaryKey": false, | |
| 2217 | + "notNull": true, | |
| 2218 | + "default": 0 | |
| 2219 | + }, | |
| 2220 | + "wagered": { | |
| 2221 | + "name": "wagered", | |
| 2222 | + "type": "bigint", | |
| 2223 | + "primaryKey": false, | |
| 2224 | + "notNull": true, | |
| 2225 | + "default": 0 | |
| 2226 | + }, | |
| 2227 | + "won": { | |
| 2228 | + "name": "won", | |
| 2229 | + "type": "bigint", | |
| 2230 | + "primaryKey": false, | |
| 2231 | + "notNull": true, | |
| 2232 | + "default": 0 | |
| 2233 | + }, | |
| 2234 | + "bonuses": { | |
| 2235 | + "name": "bonuses", | |
| 2236 | + "type": "integer", | |
| 2237 | + "primaryKey": false, | |
| 2238 | + "notNull": true, | |
| 2239 | + "default": 0 | |
| 2240 | + }, | |
| 2241 | + "biggest_win": { | |
| 2242 | + "name": "biggest_win", | |
| 2243 | + "type": "bigint", | |
| 2244 | + "primaryKey": false, | |
| 2245 | + "notNull": true, | |
| 2246 | + "default": 0 | |
| 2247 | + }, | |
| 2248 | + "biggest_multiplier": { | |
| 2249 | + "name": "biggest_multiplier", | |
| 2250 | + "type": "numeric(12, 2)", | |
| 2251 | + "primaryKey": false, | |
| 2252 | + "notNull": true, | |
| 2253 | + "default": "'0'" | |
| 2254 | + }, | |
| 2255 | + "last_played_at": { | |
| 2256 | + "name": "last_played_at", | |
| 2257 | + "type": "timestamp with time zone", | |
| 2258 | + "primaryKey": false, | |
| 2259 | + "notNull": true, | |
| 2260 | + "default": "now()" | |
| 2261 | + }, | |
| 2262 | + "first_played_at": { | |
| 2263 | + "name": "first_played_at", | |
| 2264 | + "type": "timestamp with time zone", | |
| 2265 | + "primaryKey": false, | |
| 2266 | + "notNull": true, | |
| 2267 | + "default": "now()" | |
| 2268 | + } | |
| 2269 | + }, | |
| 2270 | + "indexes": { | |
| 2271 | + "ugs_user_last_idx": { | |
| 2272 | + "name": "ugs_user_last_idx", | |
| 2273 | + "columns": [ | |
| 2274 | + { | |
| 2275 | + "expression": "user_id", | |
| 2276 | + "isExpression": false, | |
| 2277 | + "asc": true, | |
| 2278 | + "nulls": "last" | |
| 2279 | + }, | |
| 2280 | + { | |
| 2281 | + "expression": "last_played_at", | |
| 2282 | + "isExpression": false, | |
| 2283 | + "asc": true, | |
| 2284 | + "nulls": "last" | |
| 2285 | + } | |
| 2286 | + ], | |
| 2287 | + "isUnique": false, | |
| 2288 | + "concurrently": false, | |
| 2289 | + "method": "btree", | |
| 2290 | + "with": {} | |
| 2291 | + } | |
| 2292 | + }, | |
| 2293 | + "foreignKeys": { | |
| 2294 | + "user_game_stats_user_id_users_id_fk": { | |
| 2295 | + "name": "user_game_stats_user_id_users_id_fk", | |
| 2296 | + "tableFrom": "user_game_stats", | |
| 2297 | + "tableTo": "users", | |
| 2298 | + "columnsFrom": [ | |
| 2299 | + "user_id" | |
| 2300 | + ], | |
| 2301 | + "columnsTo": [ | |
| 2302 | + "id" | |
| 2303 | + ], | |
| 2304 | + "onDelete": "cascade", | |
| 2305 | + "onUpdate": "no action" | |
| 2306 | + }, | |
| 2307 | + "user_game_stats_game_id_games_id_fk": { | |
| 2308 | + "name": "user_game_stats_game_id_games_id_fk", | |
| 2309 | + "tableFrom": "user_game_stats", | |
| 2310 | + "tableTo": "games", | |
| 2311 | + "columnsFrom": [ | |
| 2312 | + "game_id" | |
| 2313 | + ], | |
| 2314 | + "columnsTo": [ | |
| 2315 | + "id" | |
| 2316 | + ], | |
| 2317 | + "onDelete": "cascade", | |
| 2318 | + "onUpdate": "no action" | |
| 2319 | + } | |
| 2320 | + }, | |
| 2321 | + "compositePrimaryKeys": { | |
| 2322 | + "user_game_stats_user_id_game_id_pk": { | |
| 2323 | + "name": "user_game_stats_user_id_game_id_pk", | |
| 2324 | + "columns": [ | |
| 2325 | + "user_id", | |
| 2326 | + "game_id" | |
| 2327 | + ] | |
| 2328 | + } | |
| 2329 | + }, | |
| 2330 | + "uniqueConstraints": {}, | |
| 2331 | + "policies": {}, | |
| 2332 | + "checkConstraints": {}, | |
| 2333 | + "isRLSEnabled": false | |
| 2334 | + }, | |
| 2335 | + "public.user_missions": { | |
| 2336 | + "name": "user_missions", | |
| 2337 | + "schema": "", | |
| 2338 | + "columns": { | |
| 2339 | + "id": { | |
| 2340 | + "name": "id", | |
| 2341 | + "type": "uuid", | |
| 2342 | + "primaryKey": true, | |
| 2343 | + "notNull": true, | |
| 2344 | + "default": "gen_random_uuid()" | |
| 2345 | + }, | |
| 2346 | + "user_id": { | |
| 2347 | + "name": "user_id", | |
| 2348 | + "type": "uuid", | |
| 2349 | + "primaryKey": false, | |
| 2350 | + "notNull": true | |
| 2351 | + }, | |
| 2352 | + "mission_key": { | |
| 2353 | + "name": "mission_key", | |
| 2354 | + "type": "varchar(48)", | |
| 2355 | + "primaryKey": false, | |
| 2356 | + "notNull": true | |
| 2357 | + }, | |
| 2358 | + "period_key": { | |
| 2359 | + "name": "period_key", | |
| 2360 | + "type": "varchar(16)", | |
| 2361 | + "primaryKey": false, | |
| 2362 | + "notNull": true | |
| 2363 | + }, | |
| 2364 | + "progress": { | |
| 2365 | + "name": "progress", | |
| 2366 | + "type": "bigint", | |
| 2367 | + "primaryKey": false, | |
| 2368 | + "notNull": true, | |
| 2369 | + "default": 0 | |
| 2370 | + }, | |
| 2371 | + "progress_set": { | |
| 2372 | + "name": "progress_set", | |
| 2373 | + "type": "text[]", | |
| 2374 | + "primaryKey": false, | |
| 2375 | + "notNull": true, | |
| 2376 | + "default": "'{}'::text[]" | |
| 2377 | + }, | |
| 2378 | + "completed_at": { | |
| 2379 | + "name": "completed_at", | |
| 2380 | + "type": "timestamp with time zone", | |
| 2381 | + "primaryKey": false, | |
| 2382 | + "notNull": false | |
| 2383 | + }, | |
| 2384 | + "claimed_at": { | |
| 2385 | + "name": "claimed_at", | |
| 2386 | + "type": "timestamp with time zone", | |
| 2387 | + "primaryKey": false, | |
| 2388 | + "notNull": false | |
| 2389 | + }, | |
| 2390 | + "expires_at": { | |
| 2391 | + "name": "expires_at", | |
| 2392 | + "type": "timestamp with time zone", | |
| 2393 | + "primaryKey": false, | |
| 2394 | + "notNull": true | |
| 2395 | + } | |
| 2396 | + }, | |
| 2397 | + "indexes": { | |
| 2398 | + "user_missions_unique_idx": { | |
| 2399 | + "name": "user_missions_unique_idx", | |
| 2400 | + "columns": [ | |
| 2401 | + { | |
| 2402 | + "expression": "user_id", | |
| 2403 | + "isExpression": false, | |
| 2404 | + "asc": true, | |
| 2405 | + "nulls": "last" | |
| 2406 | + }, | |
| 2407 | + { | |
| 2408 | + "expression": "mission_key", | |
| 2409 | + "isExpression": false, | |
| 2410 | + "asc": true, | |
| 2411 | + "nulls": "last" | |
| 2412 | + }, | |
| 2413 | + { | |
| 2414 | + "expression": "period_key", | |
| 2415 | + "isExpression": false, | |
| 2416 | + "asc": true, | |
| 2417 | + "nulls": "last" | |
| 2418 | + } | |
| 2419 | + ], | |
| 2420 | + "isUnique": true, | |
| 2421 | + "concurrently": false, | |
| 2422 | + "method": "btree", | |
| 2423 | + "with": {} | |
| 2424 | + }, | |
| 2425 | + "user_missions_user_idx": { | |
| 2426 | + "name": "user_missions_user_idx", | |
| 2427 | + "columns": [ | |
| 2428 | + { | |
| 2429 | + "expression": "user_id", | |
| 2430 | + "isExpression": false, | |
| 2431 | + "asc": true, | |
| 2432 | + "nulls": "last" | |
| 2433 | + } | |
| 2434 | + ], | |
| 2435 | + "isUnique": false, | |
| 2436 | + "concurrently": false, | |
| 2437 | + "method": "btree", | |
| 2438 | + "with": {} | |
| 2439 | + } | |
| 2440 | + }, | |
| 2441 | + "foreignKeys": { | |
| 2442 | + "user_missions_user_id_users_id_fk": { | |
| 2443 | + "name": "user_missions_user_id_users_id_fk", | |
| 2444 | + "tableFrom": "user_missions", | |
| 2445 | + "tableTo": "users", | |
| 2446 | + "columnsFrom": [ | |
| 2447 | + "user_id" | |
| 2448 | + ], | |
| 2449 | + "columnsTo": [ | |
| 2450 | + "id" | |
| 2451 | + ], | |
| 2452 | + "onDelete": "cascade", | |
| 2453 | + "onUpdate": "no action" | |
| 2454 | + }, | |
| 2455 | + "user_missions_mission_key_missions_key_fk": { | |
| 2456 | + "name": "user_missions_mission_key_missions_key_fk", | |
| 2457 | + "tableFrom": "user_missions", | |
| 2458 | + "tableTo": "missions", | |
| 2459 | + "columnsFrom": [ | |
| 2460 | + "mission_key" | |
| 2461 | + ], | |
| 2462 | + "columnsTo": [ | |
| 2463 | + "key" | |
| 2464 | + ], | |
| 2465 | + "onDelete": "cascade", | |
| 2466 | + "onUpdate": "no action" | |
| 2467 | + } | |
| 2468 | + }, | |
| 2469 | + "compositePrimaryKeys": {}, | |
| 2470 | + "uniqueConstraints": {}, | |
| 2471 | + "policies": {}, | |
| 2472 | + "checkConstraints": {}, | |
| 2473 | + "isRLSEnabled": false | |
| 2474 | + }, | |
| 2475 | + "public.user_settings": { | |
| 2476 | + "name": "user_settings", | |
| 2477 | + "schema": "", | |
| 2478 | + "columns": { | |
| 2479 | + "user_id": { | |
| 2480 | + "name": "user_id", | |
| 2481 | + "type": "uuid", | |
| 2482 | + "primaryKey": true, | |
| 2483 | + "notNull": true | |
| 2484 | + }, | |
| 2485 | + "sound_enabled": { | |
| 2486 | + "name": "sound_enabled", | |
| 2487 | + "type": "boolean", | |
| 2488 | + "primaryKey": false, | |
| 2489 | + "notNull": true, | |
| 2490 | + "default": true | |
| 2491 | + }, | |
| 2492 | + "music_volume": { | |
| 2493 | + "name": "music_volume", | |
| 2494 | + "type": "numeric(3, 2)", | |
| 2495 | + "primaryKey": false, | |
| 2496 | + "notNull": true, | |
| 2497 | + "default": "'0.6'" | |
| 2498 | + }, | |
| 2499 | + "effects_volume": { | |
| 2500 | + "name": "effects_volume", | |
| 2501 | + "type": "numeric(3, 2)", | |
| 2502 | + "primaryKey": false, | |
| 2503 | + "notNull": true, | |
| 2504 | + "default": "'0.8'" | |
| 2505 | + }, | |
| 2506 | + "master_volume": { | |
| 2507 | + "name": "master_volume", | |
| 2508 | + "type": "numeric(3, 2)", | |
| 2509 | + "primaryKey": false, | |
| 2510 | + "notNull": true, | |
| 2511 | + "default": "'0.8'" | |
| 2512 | + }, | |
| 2513 | + "reduce_motion": { | |
| 2514 | + "name": "reduce_motion", | |
| 2515 | + "type": "boolean", | |
| 2516 | + "primaryKey": false, | |
| 2517 | + "notNull": true, | |
| 2518 | + "default": false | |
| 2519 | + }, | |
| 2520 | + "animation_intensity": { | |
| 2521 | + "name": "animation_intensity", | |
| 2522 | + "type": "varchar(8)", | |
| 2523 | + "primaryKey": false, | |
| 2524 | + "notNull": true, | |
| 2525 | + "default": "'high'" | |
| 2526 | + }, | |
| 2527 | + "session_reminder_minutes": { | |
| 2528 | + "name": "session_reminder_minutes", | |
| 2529 | + "type": "integer", | |
| 2530 | + "primaryKey": false, | |
| 2531 | + "notNull": true, | |
| 2532 | + "default": 60 | |
| 2533 | + }, | |
| 2534 | + "break_reminder": { | |
| 2535 | + "name": "break_reminder", | |
| 2536 | + "type": "boolean", | |
| 2537 | + "primaryKey": false, | |
| 2538 | + "notNull": true, | |
| 2539 | + "default": true | |
| 2540 | + }, | |
| 2541 | + "leaderboard_opt_in": { | |
| 2542 | + "name": "leaderboard_opt_in", | |
| 2543 | + "type": "boolean", | |
| 2544 | + "primaryKey": false, | |
| 2545 | + "notNull": true, | |
| 2546 | + "default": true | |
| 2547 | + }, | |
| 2548 | + "updated_at": { | |
| 2549 | + "name": "updated_at", | |
| 2550 | + "type": "timestamp with time zone", | |
| 2551 | + "primaryKey": false, | |
| 2552 | + "notNull": true, | |
| 2553 | + "default": "now()" | |
| 2554 | + } | |
| 2555 | + }, | |
| 2556 | + "indexes": {}, | |
| 2557 | + "foreignKeys": { | |
| 2558 | + "user_settings_user_id_users_id_fk": { | |
| 2559 | + "name": "user_settings_user_id_users_id_fk", | |
| 2560 | + "tableFrom": "user_settings", | |
| 2561 | + "tableTo": "users", | |
| 2562 | + "columnsFrom": [ | |
| 2563 | + "user_id" | |
| 2564 | + ], | |
| 2565 | + "columnsTo": [ | |
| 2566 | + "id" | |
| 2567 | + ], | |
| 2568 | + "onDelete": "cascade", | |
| 2569 | + "onUpdate": "no action" | |
| 2570 | + } | |
| 2571 | + }, | |
| 2572 | + "compositePrimaryKeys": {}, | |
| 2573 | + "uniqueConstraints": {}, | |
| 2574 | + "policies": {}, | |
| 2575 | + "checkConstraints": {}, | |
| 2576 | + "isRLSEnabled": false | |
| 2577 | + }, | |
| 2578 | + "public.users": { | |
| 2579 | + "name": "users", | |
| 2580 | + "schema": "", | |
| 2581 | + "columns": { | |
| 2582 | + "id": { | |
| 2583 | + "name": "id", | |
| 2584 | + "type": "uuid", | |
| 2585 | + "primaryKey": true, | |
| 2586 | + "notNull": true, | |
| 2587 | + "default": "gen_random_uuid()" | |
| 2588 | + }, | |
| 2589 | + "username": { | |
| 2590 | + "name": "username", | |
| 2591 | + "type": "varchar(24)", | |
| 2592 | + "primaryKey": false, | |
| 2593 | + "notNull": true | |
| 2594 | + }, | |
| 2595 | + "username_normalized": { | |
| 2596 | + "name": "username_normalized", | |
| 2597 | + "type": "varchar(24)", | |
| 2598 | + "primaryKey": false, | |
| 2599 | + "notNull": true | |
| 2600 | + }, | |
| 2601 | + "password_hash": { | |
| 2602 | + "name": "password_hash", | |
| 2603 | + "type": "text", | |
| 2604 | + "primaryKey": false, | |
| 2605 | + "notNull": true | |
| 2606 | + }, | |
| 2607 | + "level": { | |
| 2608 | + "name": "level", | |
| 2609 | + "type": "integer", | |
| 2610 | + "primaryKey": false, | |
| 2611 | + "notNull": true, | |
| 2612 | + "default": 1 | |
| 2613 | + }, | |
| 2614 | + "xp": { | |
| 2615 | + "name": "xp", | |
| 2616 | + "type": "bigint", | |
| 2617 | + "primaryKey": false, | |
| 2618 | + "notNull": true, | |
| 2619 | + "default": 0 | |
| 2620 | + }, | |
| 2621 | + "status": { | |
| 2622 | + "name": "status", | |
| 2623 | + "type": "varchar(16)", | |
| 2624 | + "primaryKey": false, | |
| 2625 | + "notNull": true, | |
| 2626 | + "default": "'active'" | |
| 2627 | + }, | |
| 2628 | + "age_confirmed_at": { | |
| 2629 | + "name": "age_confirmed_at", | |
| 2630 | + "type": "timestamp with time zone", | |
| 2631 | + "primaryKey": false, | |
| 2632 | + "notNull": false | |
| 2633 | + }, | |
| 2634 | + "created_at": { | |
| 2635 | + "name": "created_at", | |
| 2636 | + "type": "timestamp with time zone", | |
| 2637 | + "primaryKey": false, | |
| 2638 | + "notNull": true, | |
| 2639 | + "default": "now()" | |
| 2640 | + }, | |
| 2641 | + "last_login_at": { | |
| 2642 | + "name": "last_login_at", | |
| 2643 | + "type": "timestamp with time zone", | |
| 2644 | + "primaryKey": false, | |
| 2645 | + "notNull": false | |
| 2646 | + }, | |
| 2647 | + "last_rescue_at": { | |
| 2648 | + "name": "last_rescue_at", | |
| 2649 | + "type": "timestamp with time zone", | |
| 2650 | + "primaryKey": false, | |
| 2651 | + "notNull": false | |
| 2652 | + }, | |
| 2653 | + "total_spins": { | |
| 2654 | + "name": "total_spins", | |
| 2655 | + "type": "bigint", | |
| 2656 | + "primaryKey": false, | |
| 2657 | + "notNull": true, | |
| 2658 | + "default": 0 | |
| 2659 | + }, | |
| 2660 | + "games_played": { | |
| 2661 | + "name": "games_played", | |
| 2662 | + "type": "integer", | |
| 2663 | + "primaryKey": false, | |
| 2664 | + "notNull": true, | |
| 2665 | + "default": 0 | |
| 2666 | + }, | |
| 2667 | + "biggest_win": { | |
| 2668 | + "name": "biggest_win", | |
| 2669 | + "type": "bigint", | |
| 2670 | + "primaryKey": false, | |
| 2671 | + "notNull": true, | |
| 2672 | + "default": 0 | |
| 2673 | + }, | |
| 2674 | + "biggest_multiplier": { | |
| 2675 | + "name": "biggest_multiplier", | |
| 2676 | + "type": "numeric(12, 2)", | |
| 2677 | + "primaryKey": false, | |
| 2678 | + "notNull": true, | |
| 2679 | + "default": "'0'" | |
| 2680 | + } | |
| 2681 | + }, | |
| 2682 | + "indexes": { | |
| 2683 | + "users_username_normalized_idx": { | |
| 2684 | + "name": "users_username_normalized_idx", | |
| 2685 | + "columns": [ | |
| 2686 | + { | |
| 2687 | + "expression": "username_normalized", | |
| 2688 | + "isExpression": false, | |
| 2689 | + "asc": true, | |
| 2690 | + "nulls": "last" | |
| 2691 | + } | |
| 2692 | + ], | |
| 2693 | + "isUnique": true, | |
| 2694 | + "concurrently": false, | |
| 2695 | + "method": "btree", | |
| 2696 | + "with": {} | |
| 2697 | + }, | |
| 2698 | + "users_username_idx": { | |
| 2699 | + "name": "users_username_idx", | |
| 2700 | + "columns": [ | |
| 2701 | + { | |
| 2702 | + "expression": "username", | |
| 2703 | + "isExpression": false, | |
| 2704 | + "asc": true, | |
| 2705 | + "nulls": "last" | |
| 2706 | + } | |
| 2707 | + ], | |
| 2708 | + "isUnique": true, | |
| 2709 | + "concurrently": false, | |
| 2710 | + "method": "btree", | |
| 2711 | + "with": {} | |
| 2712 | + } | |
| 2713 | + }, | |
| 2714 | + "foreignKeys": {}, | |
| 2715 | + "compositePrimaryKeys": {}, | |
| 2716 | + "uniqueConstraints": {}, | |
| 2717 | + "policies": {}, | |
| 2718 | + "checkConstraints": {}, | |
| 2719 | + "isRLSEnabled": false | |
| 2720 | + }, | |
| 2721 | + "public.wallets": { | |
| 2722 | + "name": "wallets", | |
| 2723 | + "schema": "", | |
| 2724 | + "columns": { | |
| 2725 | + "user_id": { | |
| 2726 | + "name": "user_id", | |
| 2727 | + "type": "uuid", | |
| 2728 | + "primaryKey": true, | |
| 2729 | + "notNull": true | |
| 2730 | + }, | |
| 2731 | + "balance": { | |
| 2732 | + "name": "balance", | |
| 2733 | + "type": "bigint", | |
| 2734 | + "primaryKey": false, | |
| 2735 | + "notNull": true, | |
| 2736 | + "default": 0 | |
| 2737 | + }, | |
| 2738 | + "lifetime_wagered": { | |
| 2739 | + "name": "lifetime_wagered", | |
| 2740 | + "type": "bigint", | |
| 2741 | + "primaryKey": false, | |
| 2742 | + "notNull": true, | |
| 2743 | + "default": 0 | |
| 2744 | + }, | |
| 2745 | + "lifetime_won": { | |
| 2746 | + "name": "lifetime_won", | |
| 2747 | + "type": "bigint", | |
| 2748 | + "primaryKey": false, | |
| 2749 | + "notNull": true, | |
| 2750 | + "default": 0 | |
| 2751 | + }, | |
| 2752 | + "lifetime_granted": { | |
| 2753 | + "name": "lifetime_granted", | |
| 2754 | + "type": "bigint", | |
| 2755 | + "primaryKey": false, | |
| 2756 | + "notNull": true, | |
| 2757 | + "default": 0 | |
| 2758 | + }, | |
| 2759 | + "updated_at": { | |
| 2760 | + "name": "updated_at", | |
| 2761 | + "type": "timestamp with time zone", | |
| 2762 | + "primaryKey": false, | |
| 2763 | + "notNull": true, | |
| 2764 | + "default": "now()" | |
| 2765 | + } | |
| 2766 | + }, | |
| 2767 | + "indexes": {}, | |
| 2768 | + "foreignKeys": { | |
| 2769 | + "wallets_user_id_users_id_fk": { | |
| 2770 | + "name": "wallets_user_id_users_id_fk", | |
| 2771 | + "tableFrom": "wallets", | |
| 2772 | + "tableTo": "users", | |
| 2773 | + "columnsFrom": [ | |
| 2774 | + "user_id" | |
| 2775 | + ], | |
| 2776 | + "columnsTo": [ | |
| 2777 | + "id" | |
| 2778 | + ], | |
| 2779 | + "onDelete": "cascade", | |
| 2780 | + "onUpdate": "no action" | |
| 2781 | + } | |
| 2782 | + }, | |
| 2783 | + "compositePrimaryKeys": {}, | |
| 2784 | + "uniqueConstraints": {}, | |
| 2785 | + "policies": {}, | |
| 2786 | + "checkConstraints": {}, | |
| 2787 | + "isRLSEnabled": false | |
| 2788 | + } | |
| 2789 | + }, | |
| 2790 | + "enums": {}, | |
| 2791 | + "schemas": {}, | |
| 2792 | + "sequences": {}, | |
| 2793 | + "roles": {}, | |
| 2794 | + "policies": {}, | |
| 2795 | + "views": {}, | |
| 2796 | + "_meta": { | |
| 2797 | + "columns": {}, | |
| 2798 | + "schemas": {}, | |
| 2799 | + "tables": {} | |
| 2800 | + } | |
| 2801 | +} | |
| \ No newline at end of file | ||
modified
packages/database/drizzle/meta/_journal.json
+7 −0
@@ -8,6 +8,13 @@ | ||
| 8 | 8 | "when": 1788821559876, |
| 9 | 9 | "tag": "0000_cuddly_shen", |
| 10 | 10 | "breakpoints": true |
| 11 | + }, | |
| 12 | + { | |
| 13 | + "idx": 1, | |
| 14 | + "version": "7", | |
| 15 | + "when": 1788836033256, | |
| 16 | + "tag": "0001_lumpy_hulk", | |
| 17 | + "breakpoints": true | |
| 11 | 18 | } |
| 12 | 19 | ] |
| 13 | 20 | } |
| \ No newline at end of file | ||
modified
packages/database/src/schema.ts
+31 −0
@@ -175,6 +175,37 @@ export const gameRounds = pgTable( | ||
| 175 | 175 | ], |
| 176 | 176 | ); |
| 177 | 177 | |
| 178 | +/** In-flight cash-out ("crash") rounds. Settled rounds are also written to game_rounds. */ | |
| 179 | +export const crashRounds = pgTable( | |
| 180 | + "crash_rounds", | |
| 181 | + { | |
| 182 | + id: uuid().primaryKey().defaultRandom(), | |
| 183 | + roundId: varchar({ length: 32 }).notNull(), | |
| 184 | + userId: uuid().notNull().references(() => users.id, { onDelete: "cascade" }), | |
| 185 | + gameId: uuid().notNull().references(() => games.id), | |
| 186 | + gameSlug: varchar({ length: 64 }).notNull(), | |
| 187 | + gameVersion: varchar({ length: 16 }).notNull(), | |
| 188 | + clientRoundId: uuid().notNull(), | |
| 189 | + bet: bigint({ mode: "number" }).notNull(), | |
| 190 | + crashMultiplier: numeric({ precision: 12, scale: 2 }).notNull(), | |
| 191 | + crashAtMs: integer().notNull(), | |
| 192 | + seed: text().notNull(), | |
| 193 | + commitment: varchar({ length: 64 }).notNull(), | |
| 194 | + events: jsonb().$type<unknown[]>().notNull().default(sql`'[]'::jsonb`), | |
| 195 | + autoCashout: numeric({ precision: 12, scale: 2 }), | |
| 196 | + status: varchar({ length: 12 }).notNull().default("running"), // running | cashed | crashed | |
| 197 | + cashoutMultiplier: numeric({ precision: 12, scale: 2 }), | |
| 198 | + win: bigint({ mode: "number" }).notNull().default(0), | |
| 199 | + startedAt: now(), | |
| 200 | + settledAt: ts(), | |
| 201 | + }, | |
| 202 | + (t) => [ | |
| 203 | + uniqueIndex("crash_rounds_round_id_idx").on(t.roundId), | |
| 204 | + uniqueIndex("crash_rounds_user_client_idx").on(t.userId, t.clientRoundId), | |
| 205 | + index("crash_rounds_user_status_idx").on(t.userId, t.status), | |
| 206 | + ], | |
| 207 | +); | |
| 208 | + | |
| 178 | 209 | export const gameStatistics = pgTable("game_statistics", { |
| 179 | 210 | gameId: uuid().primaryKey().references(() => games.id, { onDelete: "cascade" }), |
| 180 | 211 | launches: bigint({ mode: "number" }).notNull().default(0), |
modified
packages/database/src/sync-games.ts
+50 −2
@@ -2,8 +2,8 @@ import fs from "node:fs"; | ||
| 2 | 2 | import path from "node:path"; |
| 3 | 3 | import { createHash } from "node:crypto"; |
| 4 | 4 | import { createRequire } from "node:module"; |
| 5 | −import { GAMES, FEATURED_ORDER } from "@spinza/games"; | |
| 6 | −import { validateDefinition, type CertificationReport, type GameDefinition } from "@spinza/game-core"; | |
| 5 | +import { GAMES, FEATURED_ORDER, CRASH_GAMES } from "@spinza/games"; | |
| 6 | +import { validateDefinition, type CertificationReport, type GameDefinition, type CrashGameDefinition } from "@spinza/game-core"; | |
| 7 | 7 | import { eq, sql } from "drizzle-orm"; |
| 8 | 8 | import type { Db } from "./index"; |
| 9 | 9 | import { gameStatistics, gameVersions, games } from "./schema"; |
@@ -24,8 +24,31 @@ export function loadCertification(slug: string): CertificationReport | null { | ||
| 24 | 24 | } |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | +export function crashSummary(def: CrashGameDefinition) { | |
| 28 | + return { | |
| 29 | + kind: "crash", | |
| 30 | + category: "risk", | |
| 31 | + tagline: def.tagline, | |
| 32 | + description: def.description, | |
| 33 | + theme: def.theme, | |
| 34 | + volatility: def.volatility, | |
| 35 | + grid: { reels: 0, rows: 0 }, | |
| 36 | + minBet: def.minBet, | |
| 37 | + maxBet: def.maxBet, | |
| 38 | + maxMultiplier: def.maxMultiplier, | |
| 39 | + features: def.featureNames, | |
| 40 | + tags: def.tags, | |
| 41 | + palette: def.presentation.palette, | |
| 42 | + presentation: { frame: "glass", backdrop: def.presentation.scene, particles: "energy", ambience: def.presentation.ambience, scene: def.presentation.scene, verb: def.presentation.verb }, | |
| 43 | + isJackpot: false, | |
| 44 | + rtp: def.rtp, | |
| 45 | + }; | |
| 46 | +} | |
| 47 | + | |
| 27 | 48 | export function publicSummary(def: GameDefinition) { |
| 28 | 49 | return { |
| 50 | + kind: "slot", | |
| 51 | + category: "slots", | |
| 29 | 52 | tagline: def.tagline, |
| 30 | 53 | description: def.description, |
| 31 | 54 | theme: def.theme, |
@@ -120,5 +143,30 @@ export async function syncGames(db: Db, log: (s: string) => void = () => {}): Pr | ||
| 120 | 143 | reports.push({ slug: def.slug, version: def.version, lifecycle, certified }); |
| 121 | 144 | log(` ${certified ? "✓" : "·"} ${def.slug}@${def.version} → ${lifecycle}`); |
| 122 | 145 | } |
| 146 | + for (const def of CRASH_GAMES) { | |
| 147 | + order++; | |
| 148 | + const cert = loadCertification(def.slug); | |
| 149 | + const certified = !!cert && cert.status === "PASS" && cert.version === def.version; | |
| 150 | + const existing = await db.query.games.findFirst({ where: eq(games.slug, def.slug) }); | |
| 151 | + let lifecycle = certified ? "published" : "simulation"; | |
| 152 | + if (existing?.lifecycle === "disabled") lifecycle = "disabled"; | |
| 153 | + const summary = { ...crashSummary(def), certification: cert ? { status: cert.status, spins: cert.spins, observedRtp: cert.observedRtp, hitRate: cert.hitRate, certifiedAt: cert.certifiedAt } : null }; | |
| 154 | + const isNew = existing ? existing.isNew && Date.now() - existing.createdAt.getTime() < 14 * 86400_000 : true; | |
| 155 | + const [row] = await db | |
| 156 | + .insert(games) | |
| 157 | + .values({ slug: def.slug, name: def.name, version: def.version, lifecycle, sortOrder: order, isFeatured: false, isNew, summary, publishedAt: certified ? new Date() : null }) | |
| 158 | + .onConflictDoUpdate({ | |
| 159 | + target: games.slug, | |
| 160 | + set: { name: def.name, version: def.version, lifecycle, sortOrder: order, isNew, summary, publishedAt: certified ? sql`coalesce(${games.publishedAt}, now())` : games.publishedAt, updatedAt: new Date() }, | |
| 161 | + }) | |
| 162 | + .returning(); | |
| 163 | + await db | |
| 164 | + .insert(gameVersions) | |
| 165 | + .values({ gameId: row.id, version: def.version, definition: def as unknown as Record<string, unknown>, definitionHash: createHash("sha256").update(JSON.stringify(def)).digest("hex"), certification: (cert as unknown as Record<string, unknown>) ?? null, status: certified ? "published" : "simulation" }) | |
| 166 | + .onConflictDoUpdate({ target: [gameVersions.gameId, gameVersions.version], set: { definition: def as unknown as Record<string, unknown>, certification: (cert as unknown as Record<string, unknown>) ?? null, status: certified ? "published" : "simulation" } }); | |
| 167 | + await db.insert(gameStatistics).values({ gameId: row.id }).onConflictDoNothing(); | |
| 168 | + reports.push({ slug: def.slug, version: def.version, lifecycle, certified }); | |
| 169 | + log(` ${certified ? "✓" : "·"} ${def.slug}@${def.version} (crash) → ${lifecycle}`); | |
| 170 | + } | |
| 123 | 171 | return reports; |
| 124 | 172 | } |
modified
packages/game-core/src/client.ts
+2 −0
@@ -8,3 +8,5 @@ export type { CertificationReport, CertificationRules, ValidationIssue } from ". | ||
| 8 | 8 | export { emptyPlayerState } from "./types"; |
| 9 | 9 | export { waysCount } from "./define"; |
| 10 | 10 | export { LINES_10, LINES_20, LINES_25 } from "./evaluate"; |
| 11 | +export type { CrashGameDefinition, CrashEvent, CrashCurve, CrashScene, CrashMilestone } from "./crash/curve"; | |
| 12 | +export { multiplierAt, effectiveTime, timeForMultiplier, floorAt } from "./crash/curve"; | |
added
packages/game-core/src/crash/curve.ts
+162 −0
@@ -0,0 +1,162 @@ | ||
| 1 | +/** | |
| 2 | + * Crash curve math shared by server and browser (pure, no crypto). | |
| 3 | + */ | |
| 4 | +export type CrashScene = "sky" | "ocean" | "rocket" | "bank" | "volcano" | "blackhole" | "freefall" | "reactor" | "storm" | "elevator"; | |
| 5 | + | |
| 6 | +export type CrashCurve = | |
| 7 | + /** m(τ) = e^(k·τ) — classic steady acceleration. */ | |
| 8 | + | { type: "exp"; k: number } | |
| 9 | + /** m(τ) = 1 + a·τ^p — slow start, brutal end (freefall, black hole). */ | |
| 10 | + | { type: "power"; a: number; p: number } | |
| 11 | + /** Discrete floors: m = base^floor, one floor every `stepSeconds` (elevator). */ | |
| 12 | + | { type: "steps"; stepSeconds: number; base: number }; | |
| 13 | + | |
| 14 | +export interface CrashEventKind { | |
| 15 | + id: string; | |
| 16 | + label: string; | |
| 17 | + /** Speed factor applied to the time flow while the window is active (>1 boost, <1 cooling). */ | |
| 18 | + factor: number; | |
| 19 | + /** Window duration in seconds. */ | |
| 20 | + duration: [number, number]; | |
| 21 | + weight: number; | |
| 22 | +} | |
| 23 | + | |
| 24 | +export interface CrashEventsConfig { | |
| 25 | + /** Chance that a round contains at least one event. */ | |
| 26 | + chance: number; | |
| 27 | + /** Max events per round. */ | |
| 28 | + max: number; | |
| 29 | + /** Earliest / latest start (seconds of *real* time). */ | |
| 30 | + window: [number, number]; | |
| 31 | + kinds: CrashEventKind[]; | |
| 32 | +} | |
| 33 | + | |
| 34 | +export interface CrashMilestone { | |
| 35 | + multiplier: number; | |
| 36 | + label: string; | |
| 37 | +} | |
| 38 | + | |
| 39 | +export interface CrashGameDefinition { | |
| 40 | + kind: "crash"; | |
| 41 | + slug: string; | |
| 42 | + name: string; | |
| 43 | + version: string; | |
| 44 | + tagline: string; | |
| 45 | + description: string; | |
| 46 | + theme: string; | |
| 47 | + tags: string[]; | |
| 48 | + rtp: number; | |
| 49 | + minBet: number; | |
| 50 | + maxBet: number; | |
| 51 | + /** Hard cap on the crash multiplier. */ | |
| 52 | + maxMultiplier: number; | |
| 53 | + curve: CrashCurve; | |
| 54 | + events?: CrashEventsConfig; | |
| 55 | + /** Story beats shown as the multiplier passes them (cosmetic). */ | |
| 56 | + milestones: CrashMilestone[]; | |
| 57 | + presentation: { | |
| 58 | + scene: CrashScene; | |
| 59 | + palette: { primary: string; secondary: string; glow: string; bg: string; surface: string }; | |
| 60 | + /** Main button label. */ | |
| 61 | + verb: string; | |
| 62 | + /** Progress unit shown next to the multiplier, e.g. "m", "km", "°C", "floor". */ | |
| 63 | + unit: string; | |
| 64 | + /** Unit value at multiplier x (cosmetic mapping). */ | |
| 65 | + unitScale: number; | |
| 66 | + ambience: string; | |
| 67 | + }; | |
| 68 | + rules: string[]; | |
| 69 | + featureNames: string[]; | |
| 70 | + volatility: "low" | "medium" | "high" | "extreme"; | |
| 71 | +} | |
| 72 | + | |
| 73 | +export interface CrashEvent { | |
| 74 | + id: string; | |
| 75 | + label: string; | |
| 76 | + /** Real-time window in ms since round start. */ | |
| 77 | + startMs: number; | |
| 78 | + endMs: number; | |
| 79 | + factor: number; | |
| 80 | +} | |
| 81 | + | |
| 82 | +export interface CrashRoundSetup { | |
| 83 | + crashMultiplier: number; | |
| 84 | + /** ms of real time at which the crash occurs (derived from the curve + events). */ | |
| 85 | + crashAtMs: number; | |
| 86 | + seed: string; | |
| 87 | + commitment: string; | |
| 88 | + events: CrashEvent[]; | |
| 89 | +} | |
| 90 | + | |
| 91 | +/* ---------------------------------------------------------------- curve */ | |
| 92 | + | |
| 93 | +/** Effective ("game") time after applying event speed windows, in seconds. */ | |
| 94 | +export function effectiveTime(events: CrashEvent[], elapsedMs: number): number { | |
| 95 | + let tau = 0; | |
| 96 | + let cursor = 0; | |
| 97 | + const sorted = [...events].sort((a, b) => a.startMs - b.startMs); | |
| 98 | + for (const e of sorted) { | |
| 99 | + if (elapsedMs <= cursor) break; | |
| 100 | + const plainEnd = Math.min(e.startMs, elapsedMs); | |
| 101 | + if (plainEnd > cursor) tau += (plainEnd - cursor) / 1000; | |
| 102 | + const winEnd = Math.min(e.endMs, elapsedMs); | |
| 103 | + if (winEnd > e.startMs) tau += ((winEnd - e.startMs) / 1000) * e.factor; | |
| 104 | + cursor = Math.max(cursor, e.endMs); | |
| 105 | + } | |
| 106 | + if (elapsedMs > cursor) tau += (elapsedMs - cursor) / 1000; | |
| 107 | + return tau; | |
| 108 | +} | |
| 109 | + | |
| 110 | +function curveValue(curve: CrashCurve, tau: number): number { | |
| 111 | + switch (curve.type) { | |
| 112 | + case "exp": | |
| 113 | + return Math.exp(curve.k * tau); | |
| 114 | + case "power": | |
| 115 | + return 1 + curve.a * Math.pow(tau, curve.p); | |
| 116 | + case "steps": | |
| 117 | + return Math.pow(curve.base, Math.floor(tau / curve.stepSeconds)); | |
| 118 | + } | |
| 119 | +} | |
| 120 | + | |
| 121 | +function curveInverse(curve: CrashCurve, m: number): number { | |
| 122 | + switch (curve.type) { | |
| 123 | + case "exp": | |
| 124 | + return Math.log(Math.max(1, m)) / curve.k; | |
| 125 | + case "power": | |
| 126 | + return Math.pow(Math.max(0, m - 1) / curve.a, 1 / curve.p); | |
| 127 | + case "steps": | |
| 128 | + return Math.ceil(Math.log(Math.max(1, m)) / Math.log(curve.base)) * curve.stepSeconds; | |
| 129 | + } | |
| 130 | +} | |
| 131 | + | |
| 132 | +/** Multiplier displayed/settled at `elapsedMs` of real time (2-decimal floor). */ | |
| 133 | +export function multiplierAt(def: CrashGameDefinition, events: CrashEvent[], elapsedMs: number): number { | |
| 134 | + const tau = effectiveTime(events, Math.max(0, elapsedMs)); | |
| 135 | + const m = curveValue(def.curve, tau); | |
| 136 | + return Math.min(def.maxMultiplier, Math.floor(m * 100) / 100); | |
| 137 | +} | |
| 138 | + | |
| 139 | +/** Real time (ms) at which the curve first reaches `m`. */ | |
| 140 | +export function timeForMultiplier(def: CrashGameDefinition, events: CrashEvent[], m: number): number { | |
| 141 | + const targetTau = curveInverse(def.curve, m); | |
| 142 | + // Invert effectiveTime by walking the windows. | |
| 143 | + let tau = 0; | |
| 144 | + let cursor = 0; | |
| 145 | + const sorted = [...events].sort((a, b) => a.startMs - b.startMs); | |
| 146 | + for (const e of sorted) { | |
| 147 | + const plain = (e.startMs - cursor) / 1000; | |
| 148 | + if (tau + plain >= targetTau) return cursor + (targetTau - tau) * 1000; | |
| 149 | + tau += plain; | |
| 150 | + const win = ((e.endMs - e.startMs) / 1000) * e.factor; | |
| 151 | + if (tau + win >= targetTau) return e.startMs + ((targetTau - tau) / e.factor) * 1000; | |
| 152 | + tau += win; | |
| 153 | + cursor = e.endMs; | |
| 154 | + } | |
| 155 | + return cursor + (targetTau - tau) * 1000; | |
| 156 | +} | |
| 157 | + | |
| 158 | +/** Floor number for the elevator (cosmetic). */ | |
| 159 | +export function floorAt(def: CrashGameDefinition, m: number): number { | |
| 160 | + return def.curve.type === "steps" ? Math.round(Math.log(m) / Math.log(def.curve.base)) : 0; | |
| 161 | +} | |
| 162 | + | |
added
packages/game-core/src/crash/index.ts
+228 −0
@@ -0,0 +1,228 @@ | ||
| 1 | +/** | |
| 2 | + * Spinza Risk Games ("cash-out" / crash mechanic). | |
| 3 | + * | |
| 4 | + * A round starts, a multiplier grows with time, and the player must cash out | |
| 5 | + * before the secret crash point. The crash multiplier is drawn server-side at | |
| 6 | + * round start from a distribution with a fixed RTP for ANY cash-out strategy: | |
| 7 | + * | |
| 8 | + * P(crash ≥ x) = rtp / x for x ≥ 1, P(instant crash at 1.00) = 1 − rtp | |
| 9 | + * | |
| 10 | + * so the expected return of "cash out at x" is x · rtp / x = rtp. Pacing | |
| 11 | + * (curve shape, boost/cooling windows) never changes the odds — it only | |
| 12 | + * changes how the same multiplier value is reached over time. | |
| 13 | + */ | |
| 14 | +import { createHash, randomBytes } from "node:crypto"; | |
| 15 | +import type { Rng } from "../rng"; | |
| 16 | +import { CryptoRng } from "../rng"; | |
| 17 | + | |
| 18 | +import type { CrashGameDefinition, CrashEvent, CrashRoundSetup } from "./curve"; | |
| 19 | +import { timeForMultiplier } from "./curve"; | |
| 20 | +export * from "./curve"; | |
| 21 | + | |
| 22 | +/* -------------------------------------------------------------- drawing */ | |
| 23 | + | |
| 24 | +export function drawCrashMultiplier(def: CrashGameDefinition, rng: Rng): number { | |
| 25 | + const u = rng.float(); | |
| 26 | + if (u <= 0 || u > def.rtp) return 1; | |
| 27 | + const m = def.rtp / u; | |
| 28 | + return Math.min(def.maxMultiplier, Math.floor(m * 100) / 100); | |
| 29 | +} | |
| 30 | + | |
| 31 | +export function generateEvents(def: CrashGameDefinition, rng: Rng): CrashEvent[] { | |
| 32 | + const cfg = def.events; | |
| 33 | + if (!cfg || !rng.chance(cfg.chance)) return []; | |
| 34 | + const n = 1 + rng.int(cfg.max); | |
| 35 | + const out: CrashEvent[] = []; | |
| 36 | + let cursor = cfg.window[0] * 1000; | |
| 37 | + for (let i = 0; i < n; i++) { | |
| 38 | + const kind = cfg.kinds[rng.weighted(cfg.kinds.map((k) => k.weight))]; | |
| 39 | + const start = cursor + rng.float() * Math.max(500, (cfg.window[1] * 1000 - cursor) / (n - i)); | |
| 40 | + const dur = (kind.duration[0] + rng.float() * (kind.duration[1] - kind.duration[0])) * 1000; | |
| 41 | + out.push({ id: kind.id, label: kind.label, startMs: Math.round(start), endMs: Math.round(start + dur), factor: kind.factor }); | |
| 42 | + cursor = start + dur + 400; | |
| 43 | + if (cursor > cfg.window[1] * 1000) break; | |
| 44 | + } | |
| 45 | + return out; | |
| 46 | +} | |
| 47 | + | |
| 48 | +/** Create the secret setup for a round. `seed` must stay private until the round settles. */ | |
| 49 | +export function setupCrashRound(def: CrashGameDefinition, rng: Rng = new CryptoRng()): CrashRoundSetup { | |
| 50 | + const seed = randomBytes(16).toString("hex"); | |
| 51 | + const crashMultiplier = drawCrashMultiplier(def, rng); | |
| 52 | + const events = generateEvents(def, rng); | |
| 53 | + const commitment = createHash("sha256").update(`${seed}:${crashMultiplier.toFixed(2)}`).digest("hex"); | |
| 54 | + return { crashMultiplier, crashAtMs: timeForMultiplier(def, events, crashMultiplier), seed, commitment, events }; | |
| 55 | +} | |
| 56 | + | |
| 57 | +export function verifyCommitment(seed: string, crashMultiplier: number, commitment: string): boolean { | |
| 58 | + return createHash("sha256").update(`${seed}:${crashMultiplier.toFixed(2)}`).digest("hex") === commitment; | |
| 59 | +} | |
| 60 | + | |
| 61 | +/* ----------------------------------------------------------- simulation */ | |
| 62 | + | |
| 63 | +export interface CrashSimulationResult { | |
| 64 | + game: string; | |
| 65 | + version: string; | |
| 66 | + spins: number; | |
| 67 | + bet: number; | |
| 68 | + configuredRtp: number; | |
| 69 | + observedRtp: number; | |
| 70 | + deviation: number; | |
| 71 | + hitRate: number; | |
| 72 | + bonusRate: number; | |
| 73 | + freeSpinRate: number; | |
| 74 | + jackpotRate: number; | |
| 75 | + wagered: number; | |
| 76 | + returned: number; | |
| 77 | + averageWin: number; | |
| 78 | + medianWin: number; | |
| 79 | + maxWin: number; | |
| 80 | + maxWinMultiplier: number; | |
| 81 | + stdDev: number; | |
| 82 | + distribution: { label: string; min: number; max: number; count: number; share: number }[]; | |
| 83 | + convergence: { spins: number; rtp: number }[]; | |
| 84 | + featureCounts: Record<string, number>; | |
| 85 | + cappedRounds: number; | |
| 86 | + durationMs: number; | |
| 87 | +} | |
| 88 | + | |
| 89 | +/** | |
| 90 | + * Simulate rounds against a mixed population of players: cash-out targets are | |
| 91 | + * drawn log-uniformly between 1.05× and 50× (plus 10% "greedy" targets up to 500×). | |
| 92 | + * RTP is strategy-independent by construction; the simulation verifies the | |
| 93 | + * implementation end-to-end (drawing, capping, rounding). | |
| 94 | + */ | |
| 95 | +export function simulateCrash(def: CrashGameDefinition, opts: { spins: number; bet?: number; rng?: Rng }): CrashSimulationResult { | |
| 96 | + const rng = opts.rng ?? new CryptoRng(); | |
| 97 | + const bet = opts.bet ?? 100; | |
| 98 | + const started = Date.now(); | |
| 99 | + const n = opts.spins; | |
| 100 | + let returned = 0; | |
| 101 | + let hits = 0; | |
| 102 | + let sum = 0; | |
| 103 | + let sumSq = 0; | |
| 104 | + let maxWin = 0; | |
| 105 | + let capped = 0; | |
| 106 | + const buckets = [ | |
| 107 | + { label: "0×", min: 0, max: 0 }, | |
| 108 | + { label: "1–1.5×", min: 1, max: 1.5 }, | |
| 109 | + { label: "1.5–2×", min: 1.5, max: 2 }, | |
| 110 | + { label: "2–5×", min: 2, max: 5 }, | |
| 111 | + { label: "5–10×", min: 5, max: 10 }, | |
| 112 | + { label: "10–20×", min: 10, max: 20 }, | |
| 113 | + { label: "20–50×", min: 20, max: 50 }, | |
| 114 | + { label: "50–100×", min: 50, max: 100 }, | |
| 115 | + { label: "100–500×", min: 100, max: 500 }, | |
| 116 | + { label: "500×+", min: 500, max: Infinity }, | |
| 117 | + ]; | |
| 118 | + const counts = new Array(buckets.length).fill(0); | |
| 119 | + const convergence: { spins: number; rtp: number }[] = []; | |
| 120 | + const every = Math.max(1, Math.floor(n / 40)); | |
| 121 | + const feature: Record<string, number> = {}; | |
| 122 | + const sample: number[] = []; | |
| 123 | + for (let i = 0; i < n; i++) { | |
| 124 | + const crash = drawCrashMultiplier(def, rng); | |
| 125 | + if (crash >= def.maxMultiplier) capped++; | |
| 126 | + const greedy = rng.chance(0.1); | |
| 127 | + const target = Math.floor(Math.exp(Math.log(1.05) + rng.float() * (Math.log(greedy ? 500 : 50) - Math.log(1.05))) * 100) / 100; | |
| 128 | + const win = crash > target ? Math.round(bet * target) : 0; | |
| 129 | + const events = generateEvents(def, rng); | |
| 130 | + for (const e of events) feature[e.label] = (feature[e.label] ?? 0) + 1; | |
| 131 | + returned += win; | |
| 132 | + if (win > 0) hits++; | |
| 133 | + const m = win / bet; | |
| 134 | + sum += m; | |
| 135 | + sumSq += m * m; | |
| 136 | + if (win > maxWin) maxWin = win; | |
| 137 | + for (let b = 0; b < buckets.length; b++) if ((buckets[b].max === 0 && m === 0) || (m >= buckets[b].min && m < buckets[b].max && buckets[b].max !== 0)) { | |
| 138 | + counts[b]++; | |
| 139 | + break; | |
| 140 | + } | |
| 141 | + if (sample.length < 100_000) sample.push(m); | |
| 142 | + if ((i + 1) % every === 0 || i === n - 1) convergence.push({ spins: i + 1, rtp: returned / ((i + 1) * bet) }); | |
| 143 | + } | |
| 144 | + const mean = sum / n; | |
| 145 | + const variance = Math.max(0, sumSq / n - mean * mean); | |
| 146 | + sample.sort((a, b) => a - b); | |
| 147 | + const observedRtp = returned / (n * bet); | |
| 148 | + return { | |
| 149 | + game: def.slug, | |
| 150 | + version: def.version, | |
| 151 | + spins: n, | |
| 152 | + bet, | |
| 153 | + configuredRtp: def.rtp, | |
| 154 | + observedRtp, | |
| 155 | + deviation: observedRtp - def.rtp, | |
| 156 | + hitRate: hits / n, | |
| 157 | + bonusRate: 0, | |
| 158 | + freeSpinRate: 0, | |
| 159 | + jackpotRate: 0, | |
| 160 | + wagered: n * bet, | |
| 161 | + returned, | |
| 162 | + averageWin: returned / n, | |
| 163 | + medianWin: (sample[Math.floor(sample.length / 2)] ?? 0) * bet, | |
| 164 | + maxWin, | |
| 165 | + maxWinMultiplier: maxWin / bet, | |
| 166 | + stdDev: Math.sqrt(variance), | |
| 167 | + distribution: buckets.map((b, i) => ({ ...b, count: counts[i], share: counts[i] / n })), | |
| 168 | + convergence, | |
| 169 | + featureCounts: feature, | |
| 170 | + cappedRounds: capped, | |
| 171 | + durationMs: Date.now() - started, | |
| 172 | + }; | |
| 173 | +} | |
| 174 | + | |
| 175 | +/* --------------------------------------------------------------- define */ | |
| 176 | + | |
| 177 | +type CrashInput = Omit<CrashGameDefinition, "kind" | "featureNames" | "tags" | "minBet" | "maxBet"> & Partial<Pick<CrashGameDefinition, "featureNames" | "tags" | "minBet" | "maxBet">>; | |
| 178 | + | |
| 179 | +export function defineCrashGame(input: CrashInput): CrashGameDefinition { | |
| 180 | + return { | |
| 181 | + kind: "crash", | |
| 182 | + minBet: 10, | |
| 183 | + maxBet: 1000, | |
| 184 | + tags: ["risk", "cash-out"], | |
| 185 | + featureNames: ["Cash out anytime", "Auto cash-out", "Provably fair", ...(input.events ? ["Live events"] : [])], | |
| 186 | + ...input, | |
| 187 | + }; | |
| 188 | +} | |
| 189 | + | |
| 190 | +/* ---------------------------------------------------------- certification */ | |
| 191 | + | |
| 192 | +import { DEFAULT_CERTIFICATION_RULES, type CertificationReport, type CertificationRules } from "../validation"; | |
| 193 | + | |
| 194 | +/** Certification for crash games: the RTP must match the analytic value for the mixed-strategy population. */ | |
| 195 | +export function certifyCrash(def: CrashGameDefinition, sim: CrashSimulationResult, rules: CertificationRules = DEFAULT_CERTIFICATION_RULES): CertificationReport { | |
| 196 | + const se = sim.stdDev / Math.sqrt(sim.spins); | |
| 197 | + const tolerance = Math.min(0.015, Math.max(rules.maxDeviation, 3 * se)); | |
| 198 | + const checks = [ | |
| 199 | + { name: "definition", pass: def.rtp >= 0.94 && def.rtp <= 0.98 && def.maxMultiplier >= 10, detail: `rtp ${def.rtp}, cap ${def.maxMultiplier}×` }, | |
| 200 | + { name: "spins", pass: sim.spins >= rules.minSpins, detail: `${sim.spins.toLocaleString("en-US")} rounds` }, | |
| 201 | + { name: "rtp-deviation", pass: Math.abs(sim.deviation) <= tolerance, detail: `${(sim.deviation * 100).toFixed(3)}% (tolerance ±${(tolerance * 100).toFixed(2)}%)` }, | |
| 202 | + { name: "rtp-band", pass: sim.observedRtp >= rules.rtpBand[0] && sim.observedRtp <= rules.rtpBand[1], detail: `${(sim.observedRtp * 100).toFixed(2)}%` }, | |
| 203 | + { name: "max-win", pass: sim.maxWinMultiplier <= def.maxMultiplier + 1e-9, detail: `${sim.maxWinMultiplier.toFixed(1)}× (cap ${def.maxMultiplier}×)` }, | |
| 204 | + { name: "commitment", pass: (() => { const s = setupCrashRound(def); return verifyCommitment(s.seed, s.crashMultiplier, s.commitment) && s.crashAtMs >= 0; })(), detail: "SHA-256 commit/reveal verified" }, | |
| 205 | + ]; | |
| 206 | + return { | |
| 207 | + game: def.slug, | |
| 208 | + name: def.name, | |
| 209 | + version: def.version, | |
| 210 | + spins: sim.spins, | |
| 211 | + configuredRtp: def.rtp, | |
| 212 | + observedRtp: sim.observedRtp, | |
| 213 | + deviation: sim.deviation, | |
| 214 | + hitRate: sim.hitRate, | |
| 215 | + bonusRate: 0, | |
| 216 | + freeSpinRate: 0, | |
| 217 | + maxWinMultiplier: sim.maxWinMultiplier, | |
| 218 | + stdDev: sim.stdDev, | |
| 219 | + status: checks.every((c) => c.pass) ? "PASS" : "FAIL", | |
| 220 | + checks, | |
| 221 | + certifiedAt: new Date().toISOString(), | |
| 222 | + rules, | |
| 223 | + distribution: sim.distribution, | |
| 224 | + convergence: sim.convergence, | |
| 225 | + featureCounts: sim.featureCounts, | |
| 226 | + durationMs: sim.durationMs, | |
| 227 | + }; | |
| 228 | +} | |
modified
packages/game-core/src/index.ts
+1 −0
@@ -8,3 +8,4 @@ export * from "./engine"; | ||
| 8 | 8 | export * from "./simulation"; |
| 9 | 9 | export * from "./validation"; |
| 10 | 10 | export * from "./define"; |
| 11 | +export * from "./crash"; | |
| 11 | 12 | |