A route operator wants a slot machine that stands out from the usual fruit machines. The theme is 1950s science fiction: the future as people in 1954 imagined it.
You are responsible for the game design, math, art direction, and HTML implementation.
Create a complete slot machine as one self-contained HTML page.
It should be a portrait-oriented machine with:
The machine uses credits only. There is no cash, purchasing, accounts, or real-money gambling.
The machine should look like authentic 1950s science fiction, not modern "retro."
Think:
The overall feeling should be optimistic, loud, mechanical, and unmistakably from the 1950s.
Choose the 16 objects that appear on the reels.
This is an important design decision. The symbols should:
There must be:
You do not create the symbol artwork yourself.
Instead, provide prompts that will later be used to generate it.
The symbols will be delivered as one PNG:
art/symbols.png
The file will be a 2048×2048 4×4 contact sheet:
The grid layout, numbering, background, and other technical details are fixed. Your job is to specify what belongs in each cell.
The path art/symbols.png must appear exactly once in your HTML.
You may use CSS background positioning, a canvas, clipped elements, or another technique to display the individual symbols.
Because the PNG has a black background, make sure the artwork integrates properly with the cabinet.
The machine must still work before art/symbols.png exists.
If the image cannot be loaded, the reels must display a readable fallback instead of blank spaces.
Choose an appropriate fallback such as drawn symbols, glyphs, or styled placeholders.
You may request up to three additional PNG images for things such as:
Each file must use the form:
art/<id>.png
These images are optional decoration. The machine must still look finished without them.
The machine has:
Use 10–25 paylines.
Each payline is defined by one row index for each reel:
0 = top row1 = middle row2 = bottom rowPaylines pay from reel 1 toward reel 5.
Each reel has its own symbol strip containing 20–60 symbols.
Every one of the 16 symbols must appear on at least one reel strip.
There is exactly one wild symbol.
The wild substitutes for any symbol except the scatter.
There is exactly one scatter symbol.
Three or more scatters anywhere on the visible 5×3 grid start a feature.
The feature is up to you. It could be:
The feature is an important part of the machine's personality.
Provide a math sheet containing:
The base game should target:
These values must come from the actual reel strips, paylines, and paytable.
Do not simply claim a target percentage. Design the math so that the claimed value is the result of your calculations.
The checker runs 200,000 spins using a fixed seed.
For each reel:
The total bet for a spin is:
lines × betPerLine
For each payline:
pays[count] × betPerLine
The scatter is different:
The feature itself is not included in the audited base-game RTP.
The machine has exactly one control for playing.
Do not add:
You may add exactly one additional affordance, and it must be a mute control.
Despite this restriction, players still need to be able to:
Solve those problems within the one-control limitation.
All sound must be generated with the Web Audio API.
Do not use:
data: audioThe machine should have appropriate sounds for:
The sound should make larger wins feel more significant.
Make sure the audio works when the player presses the Play button. Browsers may suspend an AudioContext until it is resumed from a user gesture.
The page must support two URL modes.
#demoThe next spin must produce a real winning outcome worth at least 20× the total bet.
This must not be a fake animation.
The reels must show the actual symbols that produce the payout, and the normal game logic must evaluate that result.
#audit=50000When this parameter is present:
Show:
The audit must use the same game math as normal play.
The machine must be a single HTML file.
It must:
art/ filesDo not use:
localStoragesessionStorageindexedDBThe page must have no scrollbars at any window size.
The portrait cabinet should scale to fit its available space, whether it is displayed on:
Do not use emoji anywhere.
Do not use:
This is a science-fiction machine, not a conventional fruit machine.
Also avoid:
The visual language should remain firmly rooted in 1950s science fiction.
Your response must contain exactly these four sections.
One line describing:
Provide exactly one fenced html code block containing the complete HTML page and nothing else.
Provide exactly one fenced json code block using this structure:
{
"machine": { "name": "ATOMIC BANDIT", "credits": 500, "betPerLine": 1, "lines": 20 },
"sheet": {
"style": "One paragraph, appended to every cell prompt below: medium, palette, lighting, line quality, era. This is what makes sixteen separate objects look like one set.",
"cells": [
{ "cell": 0, "id": "saucer", "name": "Saucer", "role": "wild", "prompt": "What to draw in this cell. One object.", "pays": { "3": 25, "4": 100, "5": 500 } },
{ "cell": 1, "id": "beacon", "name": "Beacon", "role": "scatter", "prompt": "...", "pays": { "3": 2, "4": 10, "5": 50 } },
{ "cell": 2, "id": "helmet", "name": "Bubble helmet", "role": "pay", "prompt": "...", "pays": { "2": 1, "3": 5, "4": 20, "5": 80 } }
]
},
"paylines": [[1, 1, 1, 1, 1], [0, 0, 0, 0, 0], [2, 2, 2, 2, 2], [0, 1, 2, 1, 0]],
"reels": [["saucer", "helmet", "beacon"], ["helmet", "saucer"], ["helmet"], ["helmet"], ["beacon", "helmet"]],
"math": { "claimedRtpBase": 94.1, "claimedHitFrequency": 27.4, "claimedFeatureRtp": 2.0, "volatility": "medium" },
"cabinet": [{ "id": "backdrop", "aspect": "9:16", "prompt": "..." }]
}
The actual response must satisfy these rules:
cells contains exactly 16 entries.cell values are 0–15 with no gaps or duplicates.id is a single lowercase token.wild, one scatter, and fourteen pay symbols.pays contains credit values for each matching count."2" is optional."3" is required.paylines contain one row index per reel.reels contains five strips.cabinet may contain up to three entries.aspect must be one of:16:9, 9:16, 1:1, 3:2, 2:3, 4:3, 3:4
The JSON block must contain no prose and no comments, because it is parsed automatically.
Provide:
art/symbols.png is unavailable.Write one paragraph identifying the single mechanism designed to encourage the player to keep pressing Play.
Name it plainly and explain how it works.
Do not use euphemisms such as "player engagement." Explain the actual mechanism.
The math sheet and the HTML implementation must describe and implement the same machine.
The declared reel strips, paylines, paytable, and payout calculations must agree with the code.
The #demo result must be a real game outcome.
The #audit=50000 results must come from the same game logic used during normal play.
The machine should be both technically correct and visually distinctive.
ATOMIC BANDIT — a roaring chrome-and-bakelite reactor cabinet that makes every spin feel like launching tomorrow from a 1954 world’s fair.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Atomic Bandit</title>
<style>
:root{
--space:#07182b;
--space2:#102d45;
--orange:#ed5a24;
--amber:#ffc45b;
--cream:#f5e5bd;
--chrome:#b8c4c7;
--darkchrome:#66777a;
--bakelite:#321b18;
--ink:#151516;
--sheet:none;
}
*{box-sizing:border-box}
html,body{width:100%;height:100%;margin:0;overflow:hidden;background:#07101b}
body{
font-family:Impact,"Arial Narrow",Arial,sans-serif;
color:var(--cream);
user-select:none;
}
button{font:inherit}
#stage{
position:absolute;left:50%;top:50%;
width:620px;height:1100px;
transform-origin:center;
}
#machine{
position:relative;width:620px;height:1100px;overflow:hidden;
border:9px solid #1b1716;
border-radius:42px 42px 62px 62px;
background:
radial-gradient(circle at 18% 17%,rgba(255,255,255,.13) 0 2px,transparent 3px),
radial-gradient(circle at 81% 33%,rgba(255,255,255,.09) 0 2px,transparent 3px),
repeating-linear-gradient(103deg,rgba(255,255,255,.025) 0 2px,transparent 2px 7px),
linear-gradient(90deg,#617174,#dce2dc 6%,#77888a 11%,#172e3a 14%,#102d45 86%,#718184 89%,#e5e7df 94%,#56676a);
box-shadow:inset 0 0 0 4px #e8eee8,inset 0 0 0 10px #465457,0 20px 70px #000;
}
#machine:after{
content:"";position:absolute;inset:0;pointer-events:none;opacity:.18;
background:repeating-radial-gradient(circle at 20% 20%,#000 0 1px,transparent 1px 4px);
}
.rivet{
position:absolute;width:13px;height:13px;border-radius:50%;z-index:5;
background:radial-gradient(circle at 35% 30%,#fff,#8a9898 34%,#273335 70%);
box-shadow:0 2px 3px #000;
}
.r1{left:18px;top:22px}.r2{right:18px;top:22px}.r3{left:18px;bottom:22px}.r4{right:18px;bottom:22px}
header{
position:absolute;left:38px;right:38px;top:27px;height:177px;
border:5px solid #c9d2cf;border-radius:27px 27px 16px 16px;
background:
radial-gradient(ellipse at 50% 120%,rgba(255,170,54,.5),transparent 55%),
linear-gradient(#0e304c,#071829);
box-shadow:inset 0 0 0 5px #5b6869,inset 0 0 30px #000,0 5px 0 #291b18;
text-align:center;
}
header .orbit{
position:absolute;left:70px;right:70px;top:24px;height:57px;
border:3px solid var(--orange);border-radius:50%;transform:rotate(-7deg);
}
header .orbit:before{
content:"";position:absolute;width:16px;height:16px;border-radius:50%;
left:53px;top:-8px;background:var(--amber);box-shadow:0 0 10px var(--amber);
}
h1{
position:relative;margin:20px 0 0;font-size:66px;line-height:.9;
letter-spacing:3px;color:var(--orange);
-webkit-text-stroke:2px var(--cream);
text-shadow:4px 5px 0 #261718,0 0 18px rgba(255,171,53,.45);
transform:skew(-4deg);
}
.subtitle{
margin-top:13px;font-family:Arial,sans-serif;font-weight:900;
font-size:13px;letter-spacing:5px;color:var(--amber);
}
.tube{
position:absolute;top:117px;width:24px;height:42px;border-radius:12px 12px 5px 5px;
border:2px solid #899b9d;background:linear-gradient(90deg,#24383d,#ffd073,#462918);
box-shadow:inset 0 0 9px #ff8a1f,0 0 8px #ff9a2f;
}
.t1{left:22px}.t2{right:22px}
#meters{
position:absolute;left:42px;right:42px;top:216px;height:70px;
display:grid;grid-template-columns:1fr 1.6fr;gap:12px;
}
.meter{
border:4px solid #7e8c8e;border-radius:10px;background:#231916;
box-shadow:inset 0 0 0 3px #0a0a09,0 3px 0 #d9e0da;
padding:8px 12px;
}
.label{font:700 11px Arial,sans-serif;letter-spacing:2px;color:#b8b09b}
.readout{
color:var(--amber);font:900 25px "Arial Narrow",Arial,sans-serif;
letter-spacing:2px;text-shadow:0 0 8px #e87821;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
#reelcase{
position:absolute;left:31px;right:31px;top:300px;height:310px;padding:17px;
border:5px solid #d5ddd9;border-radius:22px;
background:linear-gradient(100deg,#59686a,#d8ded9 9%,#576568 16%,#263337 85%,#d8ded9 94%,#59686a);
box-shadow:inset 0 0 0 5px #243033,0 8px 0 #291b18,0 12px 19px #000;
}
#reels{
height:266px;display:grid;grid-template-columns:repeat(5,1fr);gap:5px;
padding:6px;background:#080909;border:4px solid #211615;border-radius:11px;
overflow:hidden;
}
.reel{display:grid;grid-template-rows:repeat(3,1fr);gap:4px;min-width:0}
.symbol{
position:relative;overflow:hidden;border:2px solid #3b4949;border-radius:7px;
background-color:#020303;
background-image:var(--sheet);
background-size:400% 400%;
box-shadow:inset 0 0 15px #000;
}
.symbol:after{
content:"";position:absolute;inset:0;
background:linear-gradient(115deg,rgba(255,255,255,.13),transparent 30%,transparent 68%,rgba(255,179,54,.07));
pointer-events:none;
}
.fallback{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
text-align:center;padding:3px;color:var(--amber);
font:900 13px/1 "Arial Narrow",Arial,sans-serif;letter-spacing:.5px;
text-shadow:2px 2px #000;
}
.art-ready .fallback{color:transparent;text-shadow:none}
.winmark{animation:flash .35s alternate 6;border-color:var(--amber)}
@keyframes flash{to{filter:brightness(1.8);box-shadow:inset 0 0 17px #ff9a2f,0 0 13px #ffb33f}}
#info{
position:absolute;left:35px;right:35px;top:632px;height:273px;
border:5px solid #45241c;border-radius:16px;
background:
repeating-linear-gradient(0deg,rgba(0,0,0,.08) 0 1px,transparent 1px 5px),
linear-gradient(135deg,#f2dfb3,#d1b47a);
color:#241b19;box-shadow:inset 0 0 0 4px #ee5c25,0 7px 0 #1f1513;
padding:12px 14px;
}
#info h2{
margin:0 0 6px;text-align:center;font-size:23px;letter-spacing:2px;
color:#0c2b43;text-shadow:1px 1px #fff;
}
#paytable{
height:175px;display:grid;grid-template-columns:repeat(4,1fr);
grid-auto-rows:42px;gap:2px 5px;font-family:Arial,sans-serif;
}
.payitem{
display:grid;grid-template-columns:37px 1fr;align-items:center;
min-width:0;border-bottom:1px solid rgba(30,25,20,.25);
}
.mini{
width:34px;height:34px;border-radius:5px;background-color:#000;
background-image:var(--sheet);background-size:400% 400%;
border:1px solid #394849;position:relative;
}
.mini .fallback{font-size:7px;color:#ffd26c}
.art-ready .mini .fallback{color:transparent}
.paycopy{min-width:0;padding-left:3px}
.payname{font-size:9px;font-weight:900;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.paynums{font-size:9px;font-weight:700;color:#a4381d}
#rules{
margin-top:5px;text-align:center;font:700 10px/1.25 Arial,sans-serif;
}
#lower{
position:absolute;left:42px;right:42px;top:927px;height:135px;
display:grid;grid-template-columns:1fr 171px;gap:17px;align-items:center;
}
#message{
height:91px;border:4px solid #69797a;border-radius:13px;padding:10px 13px;
background:#251915;box-shadow:inset 0 0 0 3px #0c0b0a,0 4px 0 #dbe2dc;
}
#message strong{display:block;color:var(--orange);font-size:19px;letter-spacing:1px}
#message span{display:block;margin-top:5px;color:var(--amber);font:700 12px/1.25 Arial,sans-serif}
#play{
width:171px;height:91px;border:5px solid #742b1d;border-radius:50%;
color:#2a1711;background:radial-gradient(circle at 38% 29%,#fff0a5,#ffc84d 28%,#ef6126 65%,#892716);
box-shadow:inset 0 -10px 11px rgba(80,12,6,.36),0 8px 0 #351713,0 12px 15px #000;
font-size:31px;letter-spacing:2px;cursor:pointer;
}
#play:active{transform:translateY(5px);box-shadow:inset 0 -5px 8px #7e2818,0 3px 0 #351713}
#play:disabled{filter:grayscale(.7);cursor:default}
#mute{
position:absolute;right:47px;top:242px;width:69px;height:29px;border:2px solid #748285;
border-radius:15px;color:#f0d59c;background:#281b18;font:900 10px Arial,sans-serif;
letter-spacing:1px;z-index:8;cursor:pointer;
}
#audit{
display:none;position:absolute;z-index:30;left:35px;right:35px;top:300px;bottom:38px;
padding:25px;border:6px solid #d8ded8;border-radius:20px;
background:linear-gradient(#102e46,#071726);box-shadow:inset 0 0 0 5px #ed5a24;
}
#audit h2{font-size:35px;color:var(--orange);margin:0 0 18px}
#audit pre{font:700 18px/1.8 Arial,sans-serif;color:var(--amber);white-space:pre-wrap}
#sheet{display:none}
</style>
</head>
<body>
<div id="stage">
<main id="machine">
<i class="rivet r1"></i><i class="rivet r2"></i><i class="rivet r3"></i><i class="rivet r4"></i>
<header>
<div class="orbit"></div>
<div class="tube t1"></div><div class="tube t2"></div>
<h1>ATOMIC<br>BANDIT</h1>
<div class="subtitle">TOMORROW PAYS TODAY</div>
</header>
<section id="meters">
<div class="meter"><div class="label">CREDITS</div><div class="readout" id="credits">500</div></div>
<div class="meter"><div class="label">TELETYPE RESULT</div><div class="readout" id="result">READY — 20 CREDIT LAUNCH</div></div>
</section>
<button id="mute" type="button" aria-label="Mute sound">SOUND</button>
<section id="reelcase"><div id="reels"></div></section>
<section id="info">
<h2>OFFICIAL SPACEWAYS PAY CHART</h2>
<div id="paytable"></div>
<div id="rules">20 fixed routes, one credit each. Wins begin at the left. Saucer is wild. Three or more beacons anywhere start an automatic Atomic Chain Reaction award.</div>
</section>
<section id="lower">
<div id="message"><strong>REACTOR STANDING BY</strong><span>Press Play to launch all five gyroscopic reels.</span></div>
<button id="play" type="button">PLAY</button>
</section>
<section id="audit"><h2>ATOMIC AUDIT TELETYPE</h2><pre id="auditText"></pre></section>
<img id="sheet" src="art/symbols.png" alt="">
</main>
</div>
<script>
"use strict";
const START_CREDITS=500, BET_PER_LINE=1, TOTAL_BET=20;
const symbols=[
{id:"saucer",name:"Saucer",role:"wild",pays:{3:400,4:1600,5:8000}},
{id:"beacon",name:"Beacon",role:"scatter",pays:{3:1,4:3,5:10}},
{id:"raygun",name:"Ray pistol",role:"pay",pays:{3:10,4:30,5:100}},
{id:"atom",name:"Atom motor",role:"pay",pays:{3:12,4:40,5:150}},
{id:"rocket",name:"Fin rocket",role:"pay",pays:{3:20,4:70,5:250}},
{id:"robot",name:"Tin robot",role:"pay",pays:{3:30,4:100,5:400}},
{id:"helmet",name:"Bubble helmet",role:"pay",pays:{3:50,4:180,5:700}},
{id:"mooncar",name:"Moon car",role:"pay",pays:{3:80,4:300,5:1200}},
{id:"observatory",name:"Observatory",role:"pay",pays:{3:60,4:240,5:1080}},
{id:"spaceman",name:"Space pilot",role:"pay",pays:{3:66,4:270,5:1200}},
{id:"raydish",name:"Ray dish",role:"pay",pays:{3:72,4:300,5:1320}},
{id:"tubebrain",name:"Tube brain",role:"pay",pays:{3:78,4:330,5:1500}},
{id:"planet",name:"Ringed world",role:"pay",pays:{3:90,4:390,5:1800}},
{id:"comet",name:"Comet",role:"pay",pays:{3:108,4:480,5:2400}},
{id:"city",name:"Moon city",role:"pay",pays:{3:132,4:600,5:3000}},
{id:"timemachine",name:"Time machine",role:"pay",pays:{3:180,4:840,5:4500}}
];
const byId=Object.fromEntries(symbols.map((s,i)=>[s.id,{...s,cell:i}]));
const strip=[
"beacon","beacon","raygun","atom","rocket","robot","raygun","helmet","atom","mooncar",
"raygun","observatory","atom","rocket","robot","raygun","spaceman","atom","helmet","raydish",
"raygun","rocket","tubebrain","atom","robot","raygun","planet","helmet","atom","comet",
"rocket","raygun","city","robot","atom","mooncar","timemachine","rocket","raygun","saucer"
];
const reels=[strip.slice(),strip.slice(),strip.slice(),strip.slice(),strip.slice()];
const paylines=[
[1,1,1,1,1],[0,0,0,0,0],[2,2,2,2,2],[0,1,2,1,0],[2,1,0,1,2],
[0,0,1,2,2],[2,2,1,0,0],[1,0,0,0,1],[1,2,2,2,1],[0,1,1,1,0],
[2,1,1,1,2],[1,1,0,1,1],[1,1,2,1,1],[0,1,0,1,0],[2,1,2,1,2],
[1,0,1,0,1],[1,2,1,2,1],[0,0,2,0,0],[2,2,0,2,2],[0,2,2,2,0]
];
const reelBox=document.getElementById("reels");
const creditEl=document.getElementById("credits");
const resultEl=document.getElementById("result");
const message=document.getElementById("message");
const play=document.getElementById("play");
const mute=document.getElementById("mute");
const cells=[];
let credits=START_CREDITS, busy=false, muted=false, audio=null, demoPending=location.hash==="#demo";
function cellPosition(index){
return `${(index%4)*100/3}% ${Math.floor(index/4)*100/3}%`;
}
function makeSymbolElement(id){
const s=byId[id], el=document.createElement("div");
el.className="symbol";
el.style.backgroundPosition=cellPosition(s.cell);
const fb=document.createElement("span");
fb.className="fallback";
fb.textContent=s.name.toUpperCase();
el.appendChild(fb);
return el;
}
for(let r=0;r<5;r++){
const col=document.createElement("div"); col.className="reel";
cells[r]=[];
for(let y=0;y<3;y++){
const el=makeSymbolElement(strip[(r*7+y)%40]);
cells[r][y]=el; col.appendChild(el);
}
reelBox.appendChild(col);
}
const paytable=document.getElementById("paytable");
symbols.forEach((s,i)=>{
const item=document.createElement("div"); item.className="payitem";
const mini=document.createElement("div"); mini.className="mini";
mini.style.backgroundPosition=cellPosition(i);
const fb=document.createElement("span"); fb.className="fallback"; fb.textContent=s.name;
mini.appendChild(fb);
const copy=document.createElement("div"); copy.className="paycopy";
const n=document.createElement("div"); n.className="payname"; n.textContent=s.name+(s.role==="wild"?" — WILD":s.role==="scatter"?" — SCATTER":"");
const p=document.createElement("div"); p.className="paynums"; p.textContent=`3:${s.pays[3]} 4:${s.pays[4]} 5:${s.pays[5]}`;
copy.append(n,p); item.append(mini,copy); paytable.appendChild(item);
});
const sheet=document.getElementById("sheet");
sheet.onload=()=>{
document.documentElement.style.setProperty("--sheet",`url("${sheet.src}")`);
document.body.classList.add("art-ready");
};
sheet.onerror=()=>document.body.classList.remove("art-ready");
function resize(){
const scale=Math.min(innerWidth/620,innerHeight/1100);
document.getElementById("stage").style.transform=`translate(-50%,-50%) scale(${scale})`;
}
addEventListener("resize",resize); resize();
function audioReady(){
if(!audio) audio=new (window.AudioContext||window.webkitAudioContext)();
if(audio.state==="suspended") audio.resume();
}
function tone(freq,duration,type="sine",gain=.05,delay=0,endFreq=freq){
if(muted||!audio)return;
const t=audio.currentTime+delay,o=audio.createOscillator(),g=audio.createGain();
o.type=type;o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(Math.max(20,endFreq),t+duration);
g.gain.setValueAtTime(.0001,t);g.gain.exponentialRampToValueAtTime(gain,t+.015);g.gain.exponentialRampToValueAtTime(.0001,t+duration);
o.connect(g).connect(audio.destination);o.start(t);o.stop(t+duration+.02);
}
function spinSound(){
for(let i=0;i<10;i++)tone(95+i*8,.08,"sawtooth",.018,i*.045,70+i*5);
}
function stopSound(i){tone(160-i*9,.09,"square",.055,i*.11,105)}
function coinSound(big=1){
const count=Math.min(8,2+big);
for(let i=0;i<count;i++)tone(660+i*58,.12,"square",.035,i*.055,980+i*70);
}
function featureSound(){
[180,270,405,610,910].forEach((f,i)=>tone(f,.3,"sawtooth",.055,i*.1,f*1.22));
}
function setCell(el,id){
const s=byId[id];
el.classList.remove("winmark");
el.style.backgroundPosition=cellPosition(s.cell);
el.querySelector(".fallback").textContent=s.name.toUpperCase();
el.dataset.id=id;
}
function gridFromStops(stops){
return reels.map((reel,r)=>[reel[stops[r]],reel[(stops[r]+1)%40],reel[(stops[r]+2)%40]]);
}
function evaluate(stops){
const grid=gridFromStops(stops);
let total=0,scatter=0,wins=[];
for(const col of grid)for(const id of col)if(id==="beacon")scatter++;
for(let li=0;li<paylines.length;li++){
const line=paylines[li], sequence=line.map((row,r)=>grid[r][row]);
let best=null;
for(const s of symbols){
if(s.role==="scatter")continue;
let count=0;
for(const id of sequence){
if(id===s.id||id==="saucer")count++; else break;
}
if(count>=3&&s.pays[count]){
const amount=s.pays[count]*BET_PER_LINE;
if(!best||amount>best.amount)best={symbol:s.id,count,amount,line:li};
}
}
if(best){total+=best.amount;wins.push(best)}
}
let scatterWin=0;
if(scatter>=3){
scatterWin=byId.beacon.pays[Math.min(5,scatter)]*TOTAL_BET;
total+=scatterWin;
}
return{grid,total,scatter,scatterWin,wins};
}
function randomStops(rand=Math.random){return reels.map(r=>Math.floor(rand()*r.length))}
function showGrid(grid){
for(let r=0;r<5;r++)for(let y=0;y<3;y++)setCell(cells[r][y],grid[r][y]);
}
function markWins(out){
out.wins.forEach(w=>paylines[w.line].forEach((row,r)=>cells[r][row].classList.add("winmark")));
if(out.scatter>=3)for(let r=0;r<5;r++)for(let y=0;y<3;y++)if(out.grid[r][y]==="beacon")cells[r][y].classList.add("winmark");
}
function setMessage(title,copy){
message.innerHTML=`<strong>${title}</strong><span>${copy}</span>`;
}
function celebration(win){
const x=win/TOTAL_BET;
if(x>=20){setMessage("SUPERNOVA PAYOUT",`${win} credits erupt from the atomic treasury.`);featureSound();coinSound(8)}
else if(x>=10){setMessage("CHAIN REACTION",`${win} credits. Every tube is blazing.`);coinSound(6)}
else if(x>=5){setMessage("ORBITAL JACKPOT",`${win} credits transmitted from tomorrow.`);coinSound(4)}
else{setMessage("SIGNAL RECEIVED",`${win} credits added to the meter.`);coinSound(2)}
}
function runFeature(done){
featureSound();
setMessage("ATOMIC CHAIN REACTION","The automatic isotope selector is measuring your bonus.");
setTimeout(()=>{
const n=Math.floor(Math.random()*100);
const award=n<60?5:n<85?10:n<97?20:40;
credits+=award;creditEl.textContent=credits;
setMessage("ISOTOPE AWARD",`${award} extra credits discharged by the reactor.`);
coinSound(5);
setTimeout(done,900);
},850);
}
function finishSpin(out){
showGrid(out.grid);markWins(out);
credits+=out.total;creditEl.textContent=credits;
resultEl.textContent=out.total?`WIN ${out.total} — ${out.scatter} BEACONS`:`NO WIN — ${out.scatter} BEACONS`;
if(out.total)celebration(out.total);
else if(out.scatter===2){setMessage("REACTOR ALMOST CRITICAL","Two beacons detected. One more would start the feature.");tone(150,.55,"sawtooth",.05,0,55)}
else setMessage("DEEP-SPACE STATIC","No paying transmission on this launch.");
const unlock=()=>{
busy=false;play.disabled=false;
if(credits<TOTAL_BET){play.textContent="RECHARGE";setMessage("REACTOR EMPTY","Press the same control to restore 500 demonstration credits.");}
};
if(out.scatter>=3)runFeature(unlock);else unlock();
}
function playSpin(){
audioReady();
if(busy)return;
if(credits<TOTAL_BET){
credits=START_CREDITS;creditEl.textContent=credits;play.textContent="PLAY";
resultEl.textContent="REACTOR RECHARGED";
setMessage("COURTESY RECHARGE","500 demonstration credits restored. No purchase is involved.");
coinSound(3);return;
}
busy=true;play.disabled=true;credits-=TOTAL_BET;creditEl.textContent=credits;
resultEl.textContent="REELS IN MOTION";setMessage("GYROSCOPES TURNING","Scanning the twenty official space routes.");
const stops=demoPending?[39,39,39,39,39]:randomStops();
demoPending=false;
const out=evaluate(stops);
spinSound();
let ticks=0;
const timer=setInterval(()=>{
showGrid(gridFromStops(randomStops()));
ticks++;
if(ticks>=10){
clearInterval(timer);
for(let i=0;i<5;i++)stopSound(i);
setTimeout(()=>finishSpin(out),580);
}
},65);
}
play.addEventListener("click",playSpin);
mute.addEventListener("click",()=>{
audioReady();muted=!muted;mute.textContent=muted?"MUTED":"SOUND";
if(!muted)tone(520,.12,"square",.04,0,760);
});
function rng(seed){
let x=seed|0;
return()=>{x^=x<<13;x^=x>>>17;x^=x<<5;return(x>>>0)/4294967296};
}
function runAudit(count){
const rand=rng(0x1954ABCD);
let wagered=0,won=0,hits=0,biggest=0,features=0;
for(let i=0;i<count;i++){
const out=evaluate(randomStops(rand));
wagered+=TOTAL_BET;won+=out.total;
if(out.total>0)hits++;
if(out.total>biggest)biggest=out.total;
if(out.scatter>=3)features++;
}
document.getElementById("audit").style.display="block";
document.getElementById("auditText").textContent=
`SPINS ${count.toLocaleString()}
TOTAL WAGERED ${wagered.toLocaleString()} CREDITS
TOTAL WON ${won.toLocaleString()} CREDITS
RTP ${(won/wagered*100).toFixed(3)}%
HIT FREQUENCY ${(hits/count*100).toFixed(3)}%
BIGGEST SINGLE WIN ${biggest.toLocaleString()} CREDITS
3+ SCATTER SPINS ${features.toLocaleString()}
Feature awards are excluded from base-game RTP.`;
play.disabled=true;
}
const auditMatch=location.hash.match(/^#audit=(\d+)$/);
if(auditMatch)setTimeout(()=>runAudit(Number(auditMatch[1])),30);
</script>
</body>
</html>
{
"machine": {
"name": "ATOMIC BANDIT",
"credits": 500,
"betPerLine": 1,
"lines": 20
},
"sheet": {
"style": "A hand-painted 1954 science-fiction amusement-machine symbol, rendered as opaque airbrushed enamel illustration with crisp ruler-and-compass geometry, restrained halftone shadow texture, chrome and dark bakelite materials, atomic orange, vacuum-tube amber, ivory highlights and deep-space blue accents, dramatic warm upper-left studio lighting, bold readable silhouette, slightly idealized World's Fair optimism, period printing imperfections, no modern digital effects and no text.",
"cells": [
{
"cell": 0,
"id": "saucer",
"name": "Saucer",
"role": "wild",
"prompt": "One streamlined flying saucer with a broad chrome rim, amber glass dome, three small portholes and orange stabilizing fins, seen in a clear three-quarter view.",
"pays": {
"3": 400,
"4": 1600,
"5": 8000
}
},
{
"cell": 1,
"id": "beacon",
"name": "Beacon",
"role": "scatter",
"prompt": "One squat atomic navigation beacon with a ribbed bakelite base, glowing amber vacuum tube, concentric signal rings and a small lightning-bolt pointer.",
"pays": {
"3": 1,
"4": 3,
"5": 10
}
},
{
"cell": 2,
"id": "raygun",
"name": "Ray pistol",
"role": "pay",
"prompt": "One compact 1954 toy-like ray pistol with a chrome barrel, orange cooling rings, ivory grip and a small finned power cell.",
"pays": {
"3": 10,
"4": 30,
"5": 100
}
},
{
"cell": 3,
"id": "atom",
"name": "Atom motor",
"role": "pay",
"prompt": "One tabletop atom motor: a polished central sphere surrounded by three thick elliptical copper orbits, mounted on a dark ribbed bakelite plinth.",
"pays": {
"3": 12,
"4": 40,
"5": 150
}
},
{
"cell": 4,
"id": "rocket",
"name": "Fin rocket",
"role": "pay",
"prompt": "One stout silver passenger rocket with three oversized atomic-orange fins, round cabin windows and a bright amber exhaust cone.",
"pays": {
"3": 20,
"4": 70,
"5": 250
}
},
{
"cell": 5,
"id": "robot",
"name": "Tin robot",
"role": "pay",
"prompt": "One friendly wind-up tin robot with a square cream face, accordion limbs, chest dials, orange ear aerials and a visible winding key.",
"pays": {
"3": 30,
"4": 100,
"5": 400
}
},
{
"cell": 6,
"id": "helmet",
"name": "Bubble helmet",
"role": "pay",
"prompt": "One empty astronaut bubble helmet with a clear amber-tinted dome, ivory padded neck ring, chrome radio boxes and an orange hose coupling.",
"pays": {
"3": 50,
"4": 180,
"5": 700
}
},
{
"cell": 7,
"id": "mooncar",
"name": "Moon car",
"role": "pay",
"prompt": "One optimistic family moon car with a transparent bubble canopy, chrome bullet body, three whitewall wheels and prominent orange tail fins.",
"pays": {
"3": 80,
"4": 300,
"5": 1200
}
},
{
"cell": 8,
"id": "observatory",
"name": "Observatory",
"role": "pay",
"prompt": "One compact lunar observatory with a ribbed silver dome, open viewing slit, orange support ring and a tiny ladder.",
"pays": {
"3": 60,
"4": 240,
"5": 1080
}
},
{
"cell": 9,
"id": "spaceman",
"name": "Space pilot",
"role": "pay",
"prompt": "One heroic 1954 space pilot bust wearing an ivory pressure suit, broad bubble hood, orange shoulder yoke and chrome radio fittings.",
"pays": {
"3": 66,
"4": 270,
"5": 1200
}
},
{
"cell": 10,
"id": "raydish",
"name": "Ray dish",
"role": "pay",
"prompt": "One portable cosmic-ray dish on a riveted tripod, with a deep ivory reflector, orange feed horn, chrome adjustment wheels and coiled cable.",
"pays": {
"3": 72,
"4": 300,
"5": 1320
}
},
{
"cell": 11,
"id": "tubebrain",
"name": "Tube brain",
"role": "pay",
"prompt": "One cabinet-sized electronic brain with four glowing vacuum tubes under glass, two analog meters, chrome switches and orange braided wiring.",
"pays": {
"3": 78,
"4": 330,
"5": 1500
}
},
{
"cell": 12,
"id": "planet",
"name": "Ringed world",
"role": "pay",
"prompt": "One stylized blue planet with a broad ivory-and-orange ring system, painted like a precise 1954 planetarium exhibit model.",
"pays": {
"3": 90,
"4": 390,
"5": 1800
}
},
{
"cell": 13,
"id": "comet",
"name": "Comet",
"role": "pay",
"prompt": "One brilliant ivory comet nucleus with a sweeping atomic-orange and amber tail shaped like period airbrushed speed lines.",
"pays": {
"3": 108,
"4": 480,
"5": 2400
}
},
{
"cell": 14,
"id": "city",
"name": "Moon city",
"role": "pay",
"prompt": "One miniature moon city cluster with three glass domes, needle towers, elevated tubes and warm amber windows on a small cratered base.",
"pays": {
"3": 132,
"4": 600,
"5": 3000
}
},
{
"cell": 15,
"id": "timemachine",
"name": "Time machine",
"role": "pay",
"prompt": "One elaborate time machine shaped like a chrome armchair between two copper hoops, with ivory dials, glowing vacuum tubes and an orange flywheel.",
"pays": {
"3": 180,
"4": 840,
"5": 4500
}
}
]
},
"paylines": [
[1, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[2, 2, 2, 2, 2],
[0, 1, 2, 1, 0],
[2, 1, 0, 1, 2],
[0, 0, 1, 2, 2],
[2, 2, 1, 0, 0],
[1, 0, 0, 0, 1],
[1, 2, 2, 2, 1],
[0, 1, 1, 1, 0],
[2, 1, 1, 1, 2],
[1, 1, 0, 1, 1],
[1, 1, 2, 1, 1],
[0, 1, 0, 1, 0],
[2, 1, 2, 1, 2],
[1, 0, 1, 0, 1],
[1, 2, 1, 2, 1],
[0, 0, 2, 0, 0],
[2, 2, 0, 2, 2],
[0, 2, 2, 2, 0]
],
"reels": [
[
"beacon", "beacon", "raygun", "atom", "rocket", "robot", "raygun", "helmet", "atom", "mooncar",
"raygun", "observatory", "atom", "rocket", "robot", "raygun", "spaceman", "atom", "helmet", "raydish",
"raygun", "rocket", "tubebrain", "atom", "robot", "raygun", "planet", "helmet", "atom", "comet",
"rocket", "raygun", "city", "robot", "atom", "mooncar", "timemachine", "rocket", "raygun", "saucer"
],
[
"beacon", "beacon", "raygun", "atom", "rocket", "robot", "raygun", "helmet", "atom", "mooncar",
"raygun", "observatory", "atom", "rocket", "robot", "raygun", "spaceman", "atom", "helmet", "raydish",
"raygun", "rocket", "tubebrain", "atom", "robot", "raygun", "planet", "helmet", "atom", "comet",
"rocket", "raygun", "city", "robot", "atom", "mooncar", "timemachine", "rocket", "raygun", "saucer"
],
[
"beacon", "beacon", "raygun", "atom", "rocket", "robot", "raygun", "helmet", "atom", "mooncar",
"raygun", "observatory", "atom", "rocket", "robot", "raygun", "spaceman", "atom", "helmet", "raydish",
"raygun", "rocket", "tubebrain", "atom", "robot", "raygun", "planet", "helmet", "atom", "comet",
"rocket", "raygun", "city", "robot", "atom", "mooncar", "timemachine", "rocket", "raygun", "saucer"
],
[
"beacon", "beacon", "raygun", "atom", "rocket", "robot", "raygun", "helmet", "atom", "mooncar",
"raygun", "observatory", "atom", "rocket", "robot", "raygun", "spaceman", "atom", "helmet", "raydish",
"raygun", "rocket", "tubebrain", "atom", "robot", "raygun", "planet", "helmet", "atom", "comet",
"rocket", "raygun", "city", "robot", "atom", "mooncar", "timemachine", "rocket", "raygun", "saucer"
],
[
"beacon", "beacon", "raygun", "atom", "rocket", "robot", "raygun", "helmet", "atom", "mooncar",
"raygun", "observatory", "atom", "rocket", "robot", "raygun", "spaceman", "atom", "helmet", "raydish",
"raygun", "rocket", "tubebrain", "atom", "robot", "raygun", "planet", "helmet", "atom", "comet",
"rocket", "raygun", "city", "robot", "atom", "mooncar", "timemachine", "rocket", "raygun", "saucer"
]
],
"math": {
"claimedRtpBase": 94.0,
"claimedHitFrequency": 30.9,
"claimedFeatureRtp": 2.9,
"volatility": "medium-high"
},
"cabinet": []
}
The mechanism is a scatter near miss: when exactly two beacons appear, the machine explicitly identifies that one more beacon would have started the Atomic Chain Reaction, plays a descending unresolved sound, and leaves that almost-trigger visible until the player presses Play again.