SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
10.1 KB · 200 lines typescript
Raw Blame History
1import { defineArcadeGame, type ArcadeGameDefinition } from "@spinza/game-core";2import calibration from "../calibration.json";34/**5 * Spinza Originals — Beyond Slots. Five interactive originals sharing the6 * arcade engine: two ladder games (analytic RTP) and three instant games7 * (payScale calibrated by simulation, see calibration.json).8 */910export const dropzone = defineArcadeGame({11  slug: "dropzone",12  name: "Dropzone",13  version: "1.0.0",14  tagline: "Release the capsule. Pray for the edge.",15  description:16    "Drop a capsule from the top of a vertical tower. It ricochets through pegs, energy gates and portals down to a row of buckets whose values depend on the risk profile you pick. Some descents trigger Deep Drop: the tower extends and the capsule keeps falling into a far riskier zone where the multipliers stack — or vanish.",17  theme: "arcade tower drop",18  mode: "instant",19  rtp: 0.96,20  maxMultiplier: 3000,21  volatility: "high",22  presentation: { scene: "dropzone", palette: { primary: "#22d3ee", secondary: "#f472b6", glow: "#67e8f9", bg: "#050a14", surface: "#0b1a2e" }, ambience: "arcade", verb: "DROP" },23  config: {24    rows: 12,25    lanes: 13,26    buckets: {27      low: [8, 3, 1.6, 1.2, 1, 0.7, 0.5, 0.7, 1, 1.2, 1.6, 3, 8],28      medium: [25, 6, 2.5, 1.4, 0.8, 0.4, 0.2, 0.4, 0.8, 1.4, 2.5, 6, 25],29      high: [120, 18, 4, 1.2, 0.4, 0.1, 0, 0.1, 0.4, 1.2, 4, 18, 120],30    },31    gateChancePerRow: 0.035,32    gateValues: [1.5, 2, 3],33    portalChancePerRow: 0.012,34    deepDropChance: 0.08,35    deepRows: 8,36    deepBuckets: [10, 4, 2, 1, 0, 1, 2, 4, 10],37  },38  featureNames: ["3 risk profiles", "Energy gates", "Portals", "Deep Drop"],39  rules: [40    "Choose a risk profile (Low / Medium / High) and a release lane, then press DROP.",41    "The capsule bounces left or right at every peg row (12 rows) and lands in one of 13 buckets.",42    "Energy gates multiply the result by ×1.5, ×2 or ×3; portals hop the capsule one to three lanes sideways.",43    "Bucket values are normalised per release lane, so every lane has the same expected value — edges are just wilder.",44    "Deep Drop (8% of drops): the tower extends by 8 rows and the capsule falls into a second bucket row that multiplies the result by 0× to 10×.",45    "Bucket values shown are already scaled to the game's certified RTP. Maximum win: 3,000× the bet.",46  ],47});4849export const gridBreak = defineArcadeGame({50  slug: "grid-break",51  name: "Grid//Break",52  version: "1.0.0",53  tagline: "One wave. Infinite chains.",54  description:55    "An 8×8 grid of energy blocks. Fire a wave down a column: every cluster it touches detonates, blocks fall, fresh ones drop in and new clusters detonate in a chain. Longer chains climb the multiplier ladder. Bombs clear 3×3, line blocks wipe a row, ×2 blocks double the step. It plays like a lightning-fast puzzle game.",56  theme: "futuristic puzzle grid",57  mode: "instant",58  rtp: 0.96,59  maxMultiplier: 2500,60  volatility: "medium",61  presentation: { scene: "gridbreak", palette: { primary: "#a3e635", secondary: "#38bdf8", glow: "#bef264", bg: "#05080c", surface: "#0e1620" }, ambience: "quantum", verb: "FIRE WAVE" },62  config: {63    size: 8,64    colors: 6,65    values: [1, 1.5, 2.5, 4, 7, 12],66    weights: [26, 22, 19, 15, 11, 7],67    specialChance: { bomb: 0.02, line: 0.012, x2: 0.02 },68    chainLadder: [1, 2, 4, 8, 16, 32, 64, 128],69    minCluster: 4,70    maxChains: 40,71  },72  featureNames: ["Chain reactions", "Bombs", "Line clears", "×2 blocks"],73  rules: [74    "Pick a column and fire the wave. Clusters of 4+ connected same-colour blocks touching that column detonate.",75    "Blocks fall, new blocks drop in; any new cluster of 4+ detonates automatically — a chain.",76    "Each chain step doubles the multiplier: ×1, ×2, ×4, ×8, ×16, ×32, ×64, ×128.",77    "Bomb blocks clear the 3×3 around them, line blocks clear their whole row, ×2 blocks double the step.",78    "Block values: the rarer the colour, the higher the value. Maximum win: 2,500× the bet.",79  ],80});8182export const theVault = defineArcadeGame({83  slug: "the-vault",84  name: "The Vault",85  version: "1.0.0",86  tagline: "Five layers. One decision at each.",87  description:88    "Face a colossal vault with five locked layers. Each layer reveals its digits one by one; when a layer opens, secure your multiplier or attempt the next lock. The deeper you go, the more spectacular the vault — and the higher the risk. Open all five in one run to trigger the fictional jackpot.",89  theme: "monumental vault",90  mode: "ladder",91  rtp: 0.96,92  maxMultiplier: 50,93  volatility: "high",94  presentation: { scene: "vault", palette: { primary: "#c9a961", secondary: "#38bdf8", glow: "#e8cf8f", bg: "#08070a", surface: "#16131a" }, ambience: "heist", verb: "OPEN NEXT LOCK", secondaryVerb: "SECURE" },95  config: {96    layers: [0.8, 0.7, 0.55, 0.4, 0.25],97    digitsPerLayer: 3,98    layerNames: ["Outer door", "Time lock", "Biometric ring", "Pressure chamber", "The core"],99  },100  featureNames: ["Secure or push", "5 layers", "Fictional jackpot"],101  rules: [102    "Place a bet. The first layer opens automatically — its 3 digits are revealed one by one.",103    "After each opened layer, SECURE your multiplier or OPEN the next lock.",104    "Chances the lock yields: 80% → 70% → 55% → 40% → 25%. Multipliers after each layer: 1.20× → 1.71× → 3.12× → 7.79× → 31.2×.",105    "A failed digit triggers the alarm: the run ends and the bet is lost.",106    "Opening all five layers in one run pays the fictional jackpot (31.2×). Expected return is 96% whatever your stopping point.",107  ],108});109110export const orbit = defineArcadeGame({111  slug: "orbit",112  name: "Orbit",113  version: "1.0.0",114  tagline: "Fire the impulse. Ride the chaos.",115  description:116    "Planets, satellites and debris circle a burning core. Aim an impulse through the orbits: every object it touches adds value, may deflect the impulse or spawn a brand-new orbit. Some rounds cascade into fifteen objects spinning at once. Supernova collapses everything and turns the survivors into multipliers.",117  theme: "orbital mechanics",118  mode: "instant",119  rtp: 0.96,120  maxMultiplier: 2000,121  volatility: "high",122  presentation: { scene: "orbit", palette: { primary: "#f0abfc", secondary: "#facc15", glow: "#f5d0fe", bg: "#07030f", surface: "#150a26" }, ambience: "space", verb: "FIRE IMPULSE" },123  config: {124    orbits: 3,125    objectsPerOrbit: [3, 6],126    beamHalfWidth: 14,127    objectTypes: [128      { id: "debris", label: "Debris", value: 0.3, weight: 34 },129      { id: "satellite", label: "Satellite", value: 0.8, weight: 26, deflect: 40 },130      { id: "planet", label: "Planet", value: 2, weight: 18, deflect: 20 },131      { id: "comet", label: "Comet", value: 4, weight: 10, spawn: true },132      { id: "gasgiant", label: "Gas giant", value: 8, weight: 5, deflect: 60 },133      { id: "quasar", label: "Quasar", value: 25, weight: 1, spawn: true },134    ],135    supernovaChance: 0.04,136    maxOrbits: 7,137  },138  featureNames: ["Aim the impulse", "Deflections", "New orbits", "Supernova"],139  rules: [140    "Choose an angle and fire. The impulse travels outward through every orbit.",141    "An object within the beam is collected: Debris 0.3×, Satellite 0.8×, Planet 2×, Comet 4×, Gas giant 8×, Quasar 25× (values scaled to the certified RTP).",142    "Satellites, planets and gas giants deflect the impulse; comets and quasars spawn a new orbit (up to 7).",143    "Supernova (4% of scoring rounds): every remaining object becomes +0.5× on the collected total.",144    "Maximum win: 2,000× the bet.",145  ],146});147148export const escape99 = defineArcadeGame({149  slug: "escape-99",150  name: "Escape 99",151  version: "1.0.0",152  tagline: "Ninety-nine floors. Cash out on any of them.",153  description:154    "A lightning-fast roguelike. Start on floor 1 of a 99-floor tower and climb room by room: chests, guardians, traps, runes, portals and forks between a safe corridor and a risky one. Cash out on any floor, or push for the summit — floor 99 triggers the final sequence.",155  theme: "roguelike tower",156  mode: "ladder",157  rtp: 0.96,158  maxMultiplier: 5000,159  volatility: "extreme",160  presentation: { scene: "escape", palette: { primary: "#34d399", secondary: "#f97316", glow: "#6ee7b7", bg: "#06090a", surface: "#0f1a16" }, ambience: "jungle", verb: "CLIMB", secondaryVerb: "CASH OUT" },161  config: {162    floors: 99,163    bands: [164      [10, 0.97],165      [25, 0.95],166      [50, 0.93],167      [75, 0.91],168      [99, 0.89],169    ],170    checkpoints: [10, 25, 50, 75, 99],171    rooms: [172      { kind: "chest", label: "Treasure room", weight: 26, detail: "a chest glints in the dark" },173      { kind: "enemy", label: "Guardian hall", weight: 22, detail: "something stirs" },174      { kind: "trap", label: "Trap corridor", weight: 18, detail: "pressure plates everywhere" },175      { kind: "multiplier", label: "Rune chamber", weight: 16, detail: "runes pulse with energy" },176      { kind: "room", label: "Quiet stairwell", weight: 18, detail: "nothing but echoes" },177    ],178    forkChance: 0.18,179    portalChance: 0.06,180  },181  featureNames: ["99 floors", "Forks", "Portals", "Checkpoints", "Final sequence"],182  rules: [183    "Place a bet: you enter floor 1 automatically. Each CLIMB advances one floor (three through a portal).",184    "Floor safety: 97% up to floor 10, 95% to 25, 93% to 50, 91% to 75, 89% beyond. Each survived floor multiplies by 1 ÷ safety.",185    "Forks offer a safe corridor (+6% safety, smaller jump) or a risky one (−18%, bigger jump) — equal expected value.",186    "CASH OUT at any floor after the first. Checkpoints (10, 25, 50, 75, 99) are marked on the tower.",187    "Floor 99 ends the run with the final sequence. Expected return is 96% for any stopping strategy; maximum win 5,000×.",188  ],189});190191type Calibration = Record<string, { payScale: number; version: string }>;192const withCalibration = (def: ArcadeGameDefinition): ArcadeGameDefinition => {193  const c = (calibration as Calibration)[def.slug];194  return c && c.version === def.version ? { ...def, payScale: c.payScale } : def;195};196197export const RAW_ARCADE_GAMES: ArcadeGameDefinition[] = [dropzone, gridBreak, theVault, orbit, escape99];198export const ARCADE_GAMES: ArcadeGameDefinition[] = RAW_ARCADE_GAMES.map(withCalibration);199export const ARCADE_BY_SLUG = new Map(ARCADE_GAMES.map((g) => [g.slug, g]));200