SPB Git

spb/spinza

Public

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>
Simon-Pierre Boucher committed (Sep 8, 2026) parent 51ee542 Browse files

24 changed files +6,600 −55

modified apps/simulator/src/cli.ts +14 −3
@@ -10,8 +10,8 @@
10 import fs from "node:fs"; 10 import fs from "node:fs";
11 import path from "node:path"; 11 import path from "node:path";
12 import { fileURLToPath } from "node:url"; 12 import { fileURLToPath } from "node:url";
13−import { certify, formatCertification, validateDefinition, DEFAULT_CERTIFICATION_RULES } from "@spinza/game-core"; 13+import { certify, certifyCrash, formatCertification, simulateCrash, validateDefinition, DEFAULT_CERTIFICATION_RULES } from "@spinza/game-core";
14−import { RAW_GAMES, GAMES } from "@spinza/games"; 14+import { RAW_GAMES, GAMES, CRASH_GAMES, CRASH_BY_SLUG } from "@spinza/games";
15 import { calibrate, simulateParallel } from "./index"; 15 import { calibrate, simulateParallel } from "./index";
16 16
17 const here = path.dirname(fileURLToPath(import.meta.url)); 17 const here = path.dirname(fileURLToPath(import.meta.url));
@@ -94,7 +94,18 @@ async function main() {
94 fs.mkdirSync(certDir, { recursive: true }); 94 fs.mkdirSync(certDir, { recursive: true });
95 const spins = num("spins", 1_000_000); 95 const spins = num("spins", 1_000_000);
96 let failed = 0; 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 const g = GAMES.find((x) => x.slug === slug)!; 109 const g = GAMES.find((x) => x.slug === slug)!;
99 console.log(`\nCertifying ${g.name} (${slug}@${g.version}) payScale=${g.payScale} — ${spins.toLocaleString("en-US")} spins`); 110 console.log(`\nCertifying ${g.name} (${slug}@${g.version}) payScale=${g.payScale} — ${spins.toLocaleString("en-US")} spins`);
100 const res = await simulateParallel(slug, { spins, payScale: g.payScale, threads, onProgress: progressBar }); 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 function focal(v: ArtVariant, W: number, H: number, showName: boolean): { cx: number; cy: number; R: number } { 94 function focal(v: ArtVariant, W: number, H: number, showName: boolean): { cx: number; cy: number; R: number } {
95 switch (v) { 95 switch (v) {
96 case "card": 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 case "tile": 98 case "tile":
99 return { cx: W / 2, cy: H / 2, R: W * 0.4 }; 99 return { cx: W / 2, cy: H / 2, R: W * 0.4 };
100 case "hero": 100 case "hero":
@@ -212,7 +212,7 @@ function NeonVault(s: Stage) {
212 <Sym s={s} id="V" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.4} rotate={-8} /> 212 <Sym s={s} id="V" x={cx - R * 0.78} y={cy - R * 0.5} size={R * 0.4} rotate={-8} />
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)`} /> 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 <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} /> 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 ) : null} 217 ) : null}
218 </g> 218 </g>
@@ -257,7 +257,7 @@ function CosmicCollapse(s: Stage) {
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} />) 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 : null} 258 : null}
259 {!compact ? ( 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 <tspan fontSize={f(R * 0.07)} fillOpacity={0.35}>×1 ×2 ×3 ×5 ×8 </tspan> 261 <tspan fontSize={f(R * 0.07)} fillOpacity={0.35}>×1 ×2 ×3 ×5 ×8 </tspan>
262 <tspan fontSize={f(R * 0.11)} fill={p.glow} fillOpacity={0.9}>×13</tspan> 262 <tspan fontSize={f(R * 0.11)} fill={p.glow} fillOpacity={0.9}>×13</tspan>
263 </text> 263 </text>
@@ -279,7 +279,7 @@ function GoldenEmperor(s: Stage) {
279 { x: cx, m: "×3" }, 279 { x: cx, m: "×3" },
280 { x: cx + R * 0.66, m: "×5" }, 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 return ( 283 return (
284 <g> 284 <g>
285 <defs> 285 <defs>
@@ -301,7 +301,7 @@ function GoldenEmperor(s: Stage) {
301 {pearls.map((o) => ( 301 {pearls.map((o) => (
302 <g key={o.m}> 302 <g key={o.m}>
303 <Sym s={s} id="W" x={o.x} y={py} size={R * 0.5} /> 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 </g> 305 </g>
306 ))} 306 ))}
307 <Sym s={s} id="S" x={cx - R * 0.7} y={cy - R * 0.4} size={R * 0.4} /> 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 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)}`; 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 const tiers = ["MINI", "MINOR", "MAJOR", "GRAND"]; 333 const tiers = ["MINI", "MINOR", "MAJOR", "GRAND"];
334 const coins = [ 334 const coins = [
335− { x: cx - R * 0.72, y: cy + R * 0.62, v: "5×" }, 335+ { x: cx - R * 0.72, y: cy + R * 0.5, v: "5×" },
336− { x: cx, y: cy + R * 0.8, v: "20×" }, 336+ { x: cx, y: cy + R * 0.66, v: "20×" },
337− { x: cx + R * 0.72, y: cy + R * 0.62, v: "10×" }, 337+ { x: cx + R * 0.72, y: cy + R * 0.5, v: "10×" },
338 ]; 338 ];
339 return ( 339 return (
340 <g> 340 <g>
@@ -360,16 +360,16 @@ function DiamondHeist(s: Stage) {
360 {coins.map((c) => ( 360 {coins.map((c) => (
361 <g key={c.v}> 361 <g key={c.v}>
362 <Sym s={s} id="D" x={c.x} y={c.y} size={R * 0.4} /> 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 </g> 364 </g>
365 ))} 365 ))}
366 {tiers.map((t, i) => { 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 const grand = i === 3; 368 const grand = i === 3;
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} />; 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} /> 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.08} size={R * 0.38} /> 372+ <Sym s={s} id="P1" x={cx + R * 1.0} y={cy - R * 1.0} size={R * 0.38} />
373 </> 373 </>
374 ) : null} 374 ) : null}
375 <path d={sparkles} fill="#fff" fillOpacity={0.85} /> 375 <path d={sparkles} fill="#fff" fillOpacity={0.85} />
@@ -416,7 +416,7 @@ function ArcticFortune(s: Stage) {
416 <Sym s={s} id="P1" x={cx + R * 0.72} y={cy + R * 0.5} size={R * 0.62} /> 416 <Sym s={s} id="P1" x={cx + R * 0.72} y={cy + R * 0.5} size={R * 0.62} />
417 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy - R * 0.45} size={R * 0.44} /> 417 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy - R * 0.45} size={R * 0.44} />
418 <Sym s={s} id="S" x={cx + R * 0.1} y={cy - R * 0.7} size={R * 0.38} /> 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 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} /> 422 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} />
@@ -472,7 +472,7 @@ function InfernoReels(s: Stage) {
472 <Sym s={s} id="S" x={cx + R * 0.92} y={cy - R * 0.75} size={R * 0.46} rotate={16} /> 472 <Sym s={s} id="S" x={cx + R * 0.92} y={cy - R * 0.75} size={R * 0.46} rotate={16} />
473 <Sym s={s} id="H1" x={cx - R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={22} /> 473 <Sym s={s} id="H1" x={cx - R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={22} />
474 <Sym s={s} id="H2" x={cx + R * 0.95} y={cy + R * 0.55} size={R * 0.42} rotate={-20} /> 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 ) : null} 477 ) : null}
478 <path d={embers} fill={p.glow} fillOpacity={0.8} /> 478 <path d={embers} fill={p.glow} fillOpacity={0.8} />
@@ -531,7 +531,7 @@ function QuantumJackpot(s: Stage) {
531 <Sym s={s} id="W" x={cx - R * 0.85} y={cy - R * 0.85} size={R * 0.48} /> 531 <Sym s={s} id="W" x={cx - R * 0.85} y={cy - R * 0.85} size={R * 0.48} />
532 <Sym s={s} id="H1" x={cx + R * 0.85} y={cy - R * 0.9} size={R * 0.44} /> 532 <Sym s={s} id="H1" x={cx + R * 0.85} y={cy - R * 0.9} size={R * 0.44} />
533 <Sym s={s} id="S" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.4} /> 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 ) : null} 536 ) : null}
537 </g> 537 </g>
@@ -603,8 +603,8 @@ function MidnightTokyo(s: Stage) {
603 /* 09 — PHARAOH PROTOCOL: pyramid with a scanning laser, artifact meter */ 603 /* 09 — PHARAOH PROTOCOL: pyramid with a scanning laser, artifact meter */
604 function PharaohProtocol(s: Stage) { 604 function PharaohProtocol(s: Stage) {
605 const { W, H, cx, cy, R, id, p, compact } = s; 605 const { W, H, cx, cy, R, id, p, compact } = s;
606− const base = cy + R * 0.85; 606+ const base = cy + R * 0.7;
607− const apex = cy - R * 0.85; 607+ const apex = cy - R * 0.98;
608 const half = R * 1.05; 608 const half = R * 1.05;
609 const tri = `${f(cx - half)},${f(base)} ${f(cx)},${f(apex)} ${f(cx + half)},${f(base)}`; 609 const tri = `${f(cx - half)},${f(base)} ${f(cx)},${f(apex)} ${f(cx + half)},${f(base)}`;
610 let bricks = ""; 610 let bricks = "";
@@ -614,7 +614,7 @@ function PharaohProtocol(s: Stage) {
614 const segs = 12; 614 const segs = 12;
615 const mw = R * 1.4; 615 const mw = R * 1.4;
616 const mx = cx - mw / 2; 616 const mx = cx - mw / 2;
617− const my = cy + R * 1.12; 617+ const my = cy + R * 0.86;
618 let segOff = ""; 618 let segOff = "";
619 let segOn = ""; 619 let segOn = "";
620 for (let i = 0; i < segs; i++) { 620 for (let i = 0; i < segs; i++) {
@@ -633,8 +633,8 @@ function PharaohProtocol(s: Stage) {
633 </clipPath> 633 </clipPath>
634 </defs> 634 </defs>
635 <rect width={W} height={H} fill={`url(#${id}-pp1)`} /> 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)`} /> 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.75)} cy={f(cy - R * 0.72)} r={f(R * 0.2)} fill="#fb923c" fillOpacity={0.9} /> 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 <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" /> 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 <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" /> 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 <polygon points={tri} fill={`url(#${id}-pp2)`} /> 640 <polygon points={tri} fill={`url(#${id}-pp2)`} />
@@ -645,14 +645,14 @@ function PharaohProtocol(s: Stage) {
645 <Sym s={s} id="W" x={cx} y={apex - R * 0.05} size={R * 0.72} plate={false} /> 645 <Sym s={s} id="W" x={cx} y={apex - R * 0.05} size={R * 0.72} plate={false} />
646 {!compact ? ( 646 {!compact ? (
647 <> 647 <>
648− <Sym s={s} id="P1" x={cx + R * 0.62} y={cy + R * 0.35} size={R * 0.62} /> 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.8} y={cy + R * 0.3} size={R * 0.5} /> 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.55} y={cy + R * 0.72} size={R * 0.36} /> 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.2} y={cy + R * 0.78} 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.15} y={cy + R * 0.72} 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 <path d={segOff} fill="#fff" fillOpacity={0.12} /> 653 <path d={segOff} fill="#fff" fillOpacity={0.12} />
654 <path d={segOn} fill={p.secondary} fillOpacity={0.95} /> 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 ) : null} 657 ) : null}
658 <path d={dust} fill={p.primary} fillOpacity={0.5} /> 658 <path d={dust} fill={p.primary} fillOpacity={0.5} />
@@ -664,7 +664,7 @@ function PharaohProtocol(s: Stage) {
664 function LuckyCircuit(s: Stage) { 664 function LuckyCircuit(s: Stage) {
665 const { W, H, cx, cy, R, id, p, compact } = s; 665 const { W, H, cx, cy, R, id, p, compact } = s;
666 const bw = R * 2.2; 666 const bw = R * 2.2;
667− const bh = R * 2.05; 667+ const bh = R * 1.9;
668 const bx = cx - bw / 2; 668 const bx = cx - bw / 2;
669 const by = cy - bh / 2 - R * 0.05; 669 const by = cy - bh / 2 - R * 0.05;
670 let scan = ""; 670 let scan = "";
@@ -698,13 +698,13 @@ function LuckyCircuit(s: Stage) {
698 <Sym s={s} id="MY" x={cx} y={cy - R * 0.08} size={R * 1.0} /> 698 <Sym s={s} id="MY" x={cx} y={cy - R * 0.08} size={R * 1.0} />
699 {!compact ? ( 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} /> 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.72} size={R * 0.48} rotate={10} /> 702+ <Sym s={s} id="P1" x={cx + R * 0.78} y={cy - R * 0.5} size={R * 0.48} rotate={10} />
703 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy + R * 0.6} size={R * 0.46} /> 703 <Sym s={s} id="H1" x={cx - R * 0.78} y={cy + R * 0.6} size={R * 0.46} />
704 <Sym s={s} id="M2" x={cx + R * 0.78} y={cy + R * 0.6} size={R * 0.46} /> 704 <Sym s={s} id="M2" x={cx + R * 0.78} y={cy + R * 0.6} size={R * 0.46} />
705 <Sym s={s} id="S" x={cx} y={cy + R * 0.72} size={R * 0.38} /> 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} /> 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.3} text="INSERT COIN" color="#fff" size={R * 0.1} weight={800} tracking="0.2em" /> 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 ) : null} 709 ) : null}
710 </g> 710 </g>
@@ -731,8 +731,8 @@ function VoidMiner(s: Stage) {
731 [cx + R * 0.7, cy + R * 0.95, R * 0.3, 8], 731 [cx + R * 0.7, cy + R * 0.95, R * 0.3, 8],
732 [cx - R * 0.15, cy - R * 1.12, R * 0.16, 7], 732 [cx - R * 0.15, cy - R * 1.12, R * 0.16, 7],
733 ] as const; 733 ] as const;
734− const drillX = cx + R * 0.72; 734+ const drillX = cx + R * 0.6;
735− const drillY = cy - R * 0.72; 735+ const drillY = cy - R * 0.58;
736 const circ = 2 * PI * R * 0.66; 736 const circ = 2 * PI * R * 0.66;
737 const seg = circ / 5; 737 const seg = circ / 5;
738 return ( 738 return (
@@ -755,8 +755,8 @@ function VoidMiner(s: Stage) {
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" /> 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 <Sym s={s} id="W" x={drillX} y={drillY} size={R * 0.56} rotate={30} /> 756 <Sym s={s} id="W" x={drillX} y={drillY} size={R * 0.56} rotate={30} />
757 <Sym s={s} id="P1" x={cx - R * 0.72} y={cy - R * 0.5} size={R * 0.56} /> 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} /> 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.15} y={cy + R * 0.95} size={R * 0.4} /> 759+ <Sym s={s} id="H2" x={cx + R * 0.2} y={cy + R * 0.82} size={R * 0.4} />
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} /> 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 <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} /> 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 <path d={track} stroke="#1d1d26" strokeWidth={f(R * 0.56)} fill="none" strokeLinecap="round" /> 796 <path d={track} stroke="#1d1d26" strokeWidth={f(R * 0.56)} fill="none" strokeLinecap="round" />
797 <path d={track} stroke="#fff" strokeOpacity={0.35} strokeWidth={2} fill="none" strokeDasharray="14 18" /> 797 <path d={track} stroke="#fff" strokeOpacity={0.35} strokeWidth={2} fill="none" strokeDasharray="14 18" />
798 <path d={streaks} stroke={p.primary} strokeOpacity={0.55} strokeWidth={2} strokeLinecap="round" /> 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 <Sym s={s} id="W" x={cx + R * 0.1} y={cy + R * 0.08} size={R * 0.96} rotate={-10} /> 800 <Sym s={s} id="W" x={cx + R * 0.1} y={cy + R * 0.08} size={R * 0.96} rotate={-10} />
801 {!compact ? ( 801 {!compact ? (
802 <> 802 <>
@@ -831,7 +831,7 @@ function DragonCore(s: Stage) {
831 const scales = [ 831 const scales = [
832 { x: cx + R * 0.95, y: cy + R * 0.45, r: 15 }, 832 { x: cx + R * 0.95, y: cy + R * 0.45, r: 15 },
833 { x: cx + R * 0.72, y: cy + R * 0.8, r: 30 }, 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 return ( 836 return (
837 <g> 837 <g>
@@ -854,7 +854,7 @@ function DragonCore(s: Stage) {
854 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.56} rotate={-12} /> 854 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.7} size={R * 0.56} rotate={-12} />
855 <Sym s={s} id="S" x={cx - R * 0.95} y={cy - R * 0.75} size={R * 0.42} /> 855 <Sym s={s} id="S" x={cx - R * 0.95} y={cy - R * 0.75} size={R * 0.42} />
856 <Sym s={s} id="H1" x={cx + R * 0.95} y={cy - R * 0.75} size={R * 0.42} /> 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 ) : null} 859 ) : null}
860 <path d={embers} fill={p.secondary} fillOpacity={0.75} /> 860 <path d={embers} fill={p.secondary} fillOpacity={0.75} />
@@ -869,7 +869,7 @@ function DeepTreasure(s: Stage) {
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]); 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 const levels = ["×1", "×2", "×3", "×4", "×6", "×8", "×10"]; 870 const levels = ["×1", "×2", "×3", "×4", "×6", "×8", "×10"];
871 const top = cy - R * 1.05; 871 const top = cy - R * 1.05;
872− const lh = R * 0.36; 872+ const lh = R * 0.3;
873 const chest = (x: number, y: number, w: number, key: string) => ( 873 const chest = (x: number, y: number, w: number, key: string) => (
874 <g key={key}> 874 <g key={key}>
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} /> 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 : null} 896 : null}
897 {!compact ? chest(cx - R * 0.78, cy - R * 0.5, R * 0.42, "c1") : null} 897 {!compact ? chest(cx - R * 0.78, cy - R * 0.5, R * 0.42, "c1") : null}
898 {!compact ? chest(cx - R * 0.3, cy + R * 0.15, R * 0.5, "c2") : null} 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 <Sym s={s} id="P1" x={cx + R * 0.3} y={cy + R * 0.2} size={R * 0.9} /> 900 <Sym s={s} id="P1" x={cx + R * 0.3} y={cy + R * 0.2} size={R * 0.9} />
901 {!compact ? ( 901 {!compact ? (
902 <> 902 <>
@@ -1007,7 +1007,7 @@ function WildTemple(s: Stage) {
1007 <Sym s={s} id="H1" x={cx + R * 0.98} y={cy + R * 0.62} size={R * 0.5} /> 1007 <Sym s={s} id="H1" x={cx + R * 0.98} y={cy + R * 0.62} size={R * 0.5} />
1008 <Sym s={s} id="H2" x={cx - R * 1.05} y={cy - R * 0.45} size={R * 0.4} /> 1008 <Sym s={s} id="H2" x={cx - R * 1.05} y={cy - R * 0.45} size={R * 0.4} />
1009 <Sym s={s} id="H2" x={cx + R * 1.05} y={cy - R * 0.45} size={R * 0.4} /> 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 ) : null} 1012 ) : null}
1013 <path d={motes} fill={p.secondary} fillOpacity={0.55} /> 1013 <path d={motes} fill={p.secondary} fillOpacity={0.55} />
@@ -1057,7 +1057,7 @@ function ZeroGravity(s: Stage) {
1057 <Sym s={s} id="P1" x={cx - R * 0.95} y={cy + R * 0.02} size={R * 0.48} /> 1057 <Sym s={s} id="P1" x={cx - R * 0.95} y={cy + R * 0.02} size={R * 0.48} />
1058 <Sym s={s} id="S" x={cx + R * 1.05} y={cy + R * 0.42} size={R * 0.42} /> 1058 <Sym s={s} id="S" x={cx + R * 1.05} y={cy + R * 0.42} size={R * 0.42} />
1059 <Sym s={s} id="H2" x={cx + R * 0.02} y={cy - R * 1.12} size={R * 0.38} rotate={12} /> 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 ) : null} 1062 ) : null}
1063 </g> 1063 </g>
@@ -1073,7 +1073,7 @@ function ReelReactor(s: Stage) {
1073 const at = (t: number) => a0 + (a1 - a0) * t; 1073 const at = (t: number) => a0 + (a1 - a0) * t;
1074 const needle = at(0.72); 1074 const needle = at(0.72);
1075 let hazard = ""; 1075 let hazard = "";
1076− const hy = cy + R * 1.08; 1076+ const hy = cy + R * 0.95;
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 `; 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 const tick = (t: number, label: string, color: string) => { 1078 const tick = (t: number, label: string, color: string) => {
1079 const a = at(t); 1079 const a = at(t);
@@ -1096,7 +1096,7 @@ function ReelReactor(s: Stage) {
1096 {!compact ? ( 1096 {!compact ? (
1097 <> 1097 <>
1098 <Small x={cx} y={cy + R * 0.62} text="HEAT 72" color={p.secondary} size={R * 0.12} weight={800} tracking="0.06em" /> 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 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.62} size={R * 0.5} /> 1100 <Sym s={s} id="W" x={cx - R * 0.9} y={cy + R * 0.62} size={R * 0.5} />
1101 <Sym s={s} id="S" x={cx + R * 0.9} y={cy + R * 0.62} size={R * 0.5} rotate={15} /> 1101 <Sym s={s} id="S" x={cx + R * 0.9} y={cy + R * 0.62} size={R * 0.5} rotate={15} />
1102 <Sym s={s} id="H1" x={cx - R * 1.05} y={cy - R * 0.55} size={R * 0.4} /> 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 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} plate={false} /> 1157 <Sym s={s} id="W" x={cx} y={cy} size={R * 0.9} plate={false} />
1158 {!compact ? ( 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 <Sym s={s} id="MY" x={cx - R * 0.98} y={cy - R * 0.62} size={R * 0.62} /> 1161 <Sym s={s} id="MY" x={cx - R * 0.98} y={cy - R * 0.62} size={R * 0.62} />
1162 <Sym s={s} id="Z" x={cx - R * 0.98} y={cy + R * 0.3} size={R * 0.5} /> 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 {fall.map((idd, i) => ( 1164 {fall.map((idd, i) => (
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} /> 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} /> 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.3} y={cy - R * 1.18} text="MULTIPLIER DROP" color={p.glow} ink={p.glow} 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.2} text="×1 ×2 ×3 ×4 ×6 ×8 ×10 ×15" color={p.primary} size={R * 0.07} tracking="0.16em" opacity={0.7} /> 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 ) : null} 1171 ) : null}
1172 </g> 1172 </g>
@@ -1308,13 +1308,13 @@ function Title({ s, name, showName }: { s: Stage; name: string; showName: boolea
1308 const eyebrow = spec.eyebrow && cfg.eye > 0 && v !== "tile"; 1308 const eyebrow = spec.eyebrow && cfg.eye > 0 && v !== "tile";
1309 const eyeSize = v === "hero" ? size * 0.14 : v === "banner" ? size * 0.15 : Math.max(9.5, size * 0.2); 1309 const eyeSize = v === "hero" ? size * 0.14 : v === "banner" ? size * 0.15 : Math.max(9.5, size * 0.2);
1310 const fontStyle: React.CSSProperties = { fontFamily: FONT, letterSpacing: `${tracking}em` }; 1310 const fontStyle: React.CSSProperties = { fontFamily: FONT, letterSpacing: `${tracking}em` };
1311− const transform = spec.skew ? `skewX(${spec.skew})` : undefined;
1312 1311
1313 const render = (props: React.SVGProps<SVGTextElement>, key: string) => 1312 const render = (props: React.SVGProps<SVGTextElement>, key: string) =>
1314 lines.map((l, i) => { 1313 lines.map((l, i) => {
1315 const isSub = spec.subline && i === 1; 1314 const isSub = spec.subline && i === 1;
1316 const y = isSub ? baseY + subSize * 1.7 : baseY + i * lineH; 1315 const y = isSub ? baseY + subSize * 1.7 : baseY + i * lineH;
1317 const base: React.CSSProperties = isSub ? { fontFamily: FONT, letterSpacing: "0.32em" } : fontStyle; 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 return ( 1318 return (
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 }}> 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 {l} 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
Large diff (297 lines) is not loaded by default to keep the page fast.Load diff
added games/certifications/freefall.json +299 −0
Large diff (299 lines) is not loaded by default to keep the page fast.Load diff
added games/certifications/rocket-run.json +299 −0
Large diff (299 lines) is not loaded by default to keep the page fast.Load diff
added games/certifications/skyfall.json +299 −0
Large diff (299 lines) is not loaded by default to keep the page fast.Load diff
added games/certifications/storm-chase.json +299 −0
Large diff (299 lines) is not loaded by default to keep the page fast.Load diff
added games/certifications/volcano.json +299 −0
Large diff (299 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/client.ts +1 −0
Large diff (4 lines) is not loaded by default to keep the page fast.Load diff
added games/src/crash/index.ts +238 −0
Large diff (238 lines) is not loaded by default to keep the page fast.Load diff
modified games/src/index.ts +1 −0
Large diff (4 lines) is not loaded by default to keep the page fast.Load diff
added packages/database/drizzle/0001_lumpy_hulk.sql +27 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added packages/database/drizzle/meta/0001_snapshot.json +2801 −0
Large diff (2,802 lines) is not loaded by default to keep the page fast.Load diff
modified packages/database/drizzle/meta/_journal.json +7 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
modified packages/database/src/schema.ts +31 −0
Large diff (37 lines) is not loaded by default to keep the page fast.Load diff
modified packages/database/src/sync-games.ts +50 −2
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
modified packages/game-core/src/client.ts +2 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
added packages/game-core/src/crash/curve.ts +162 −0
Large diff (162 lines) is not loaded by default to keep the page fast.Load diff
added packages/game-core/src/crash/index.ts +228 −0
Large diff (228 lines) is not loaded by default to keep the page fast.Load diff
modified packages/game-core/src/index.ts +1 −0
Large diff (5 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme