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 riveted chrome upright that feels like a 1954 World’s Fair exhibit someone bolted to a boardwalk and dared to hum.
<!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>
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#071018;font-family:Palatino,"Palatino Linotype","Times New Roman",serif;color:#f3e6c4}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{background:
radial-gradient(circle at 50% 18%,rgba(240,160,48,.08),transparent 28%),
repeating-radial-gradient(circle at 50% 42%,transparent 0 18px,rgba(180,210,230,.035) 19px,transparent 20px),
linear-gradient(180deg,#10263a 0%,#071018 42%,#04080c 100%)}
#stage{position:fixed;inset:0;overflow:hidden}
#cab{position:absolute;left:50%;top:50%;width:420px;height:760px;transform-origin:center center;
background:linear-gradient(90deg,#2a2420 0%,#14110f 6%,#1b1714 94%,#2a2420 100%);
border:3px solid #c9c2b3;border-radius:18px;box-shadow:0 0 0 2px #6a655c,0 28px 60px #000a,inset 0 0 0 1px #8a8478;
overflow:hidden}
#cab:before{content:"";position:absolute;inset:8px;border:1px solid #5a4630;border-radius:12px;pointer-events:none;
background:repeating-linear-gradient(90deg,transparent 0 22px,rgba(230,220,200,.09) 22px 24px,transparent 24px 46px)}
.rivet{position:absolute;width:8px;height:8px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#f2eee4,#8a867c 55%,#3a3834);
box-shadow:0 1px 1px #0008}
.topper{position:relative;height:92px;margin:14px 16px 0;background:
linear-gradient(180deg,#d9d3c6,#8f897c 40%,#cfc8ba 70%,#6e685e);
border-radius:10px 10px 4px 4px;box-shadow:inset 0 2px 0 #fff8,0 3px 0 #3a342c;display:flex;align-items:center;justify-content:center}
.topper-art{position:absolute;inset:0;background-size:cover;background-position:center;opacity:.35;border-radius:inherit;pointer-events:none}
.tubes{position:absolute;left:10px;top:10px;display:flex;gap:6px}
.tubes.r{left:auto;right:10px}
.tube{width:12px;height:54px;border-radius:6px 6px 3px 3px;background:linear-gradient(90deg,#2a1608,#c87830 42%,#fbe7a0 50%,#c87830 58%,#2a1608);
box-shadow:0 0 10px #f0a03099;animation:glow 1.8s ease-in-out infinite alternate}
.tube:nth-child(2){animation-delay:.4s;height:46px;margin-top:8px}
.tube:nth-child(3){animation-delay:.9s}
@keyframes glow{from{filter:brightness(.75)}to{filter:brightness(1.25)}}
h1{margin:0;text-align:center;letter-spacing:.14em;line-height:.9;position:relative;z-index:1}
h1 span{display:block}
h1 .a{font-size:22px;color:#e24a12;text-shadow:0 1px 0 #3a1008,0 0 12px #ff6a20;font-weight:700}
h1 .b{font-size:28px;color:#1b140c;text-shadow:0 1px 0 #efe8d8}
.plate{margin:8px 18px 0;background:linear-gradient(180deg,#3a2a18,#24180e);border:1px solid #c9a227;border-radius:4px;
color:#e8c36a;font-size:10px;letter-spacing:.08em;text-align:center;padding:5px 8px;line-height:1.35}
.bezel{margin:8px 18px 0;height:248px;background:linear-gradient(180deg,#dcd6c8,#7a756c 14%,#111 16%,#0a0c10 84%,#7a756c 86%,#dcd6c8);
border-radius:8px;padding:16px 10px;box-shadow:inset 0 0 0 2px #4a453c,0 4px 0 #2a261e;position:relative}
.reels{height:100%;display:grid;grid-template-columns:repeat(5,1fr);gap:4px;background:#05070a;border:2px solid #2a2010;overflow:hidden}
.reel{position:relative;overflow:hidden;background:linear-gradient(180deg,#1a1008,#0b0d12 20%,#0b0d12 80%,#1a1008);border-left:1px solid #2c2418}
.reel:first-child{border-left:0}
.cell{height:33.333%;display:flex;align-items:center;justify-content:center;position:relative;border-bottom:1px solid #16120c}
.cell:last-child{border-bottom:0}
.cell-face{width:86%;height:86%;border-radius:4px;display:flex;flex-direction:column;align-items:center;justify-content:center;
background:#0c0a08;box-shadow:inset 0 0 0 1px #2a2014;overflow:hidden;position:relative}
.cell.win .cell-face{box-shadow:inset 0 0 0 2px #f0a030,0 0 10px #f0a030aa;animation:winpop .35s ease}
@keyframes winpop{from{transform:scale(.86)}to{transform:scale(1)}}
.fb{font-size:8px;letter-spacing:.08em;color:#f3e6c4;text-transform:uppercase;text-align:center;line-height:1.1;padding:2px}
.mark{width:22px;height:22px;margin-bottom:3px;position:relative}
.saucer .mark{border:2px solid #d8d2c6;border-radius:50%;box-shadow:0 8px 0 -4px #c9c2b3,0 8px 0 -2px #8a8478}
.beacon .mark{width:10px;height:22px;background:#e24a12;clip-path:polygon(50% 0,100% 100%,0 100%)}
.rocket .mark{width:12px;height:22px;background:#d8d2c6;clip-path:polygon(50% 0,100% 78%,70% 78%,70% 100%,30% 100%,30% 78%,0 78%)}
.robot .mark{background:#b8b0a0;border-radius:2px}
.robot .mark:after{content:"";position:absolute;inset:4px 3px 8px;background:#1b140c}
.helmet .mark{border:2px solid #e8c36a;border-radius:50% 50% 40% 40%;background:#9ad 4}
.atom .mark{border:2px solid #f0a030;border-radius:50%}
.atom .mark:after{content:"";position:absolute;left:8px;top:8px;width:6px;height:6px;background:#e24a12;border-radius:50%}
.orbiter .mark{background:#c9c2b3;border-radius:50%;box-shadow:8px 0 0 -4px #c9c2b3,-8px 0 0 -4px #c9c2b3,0 8px 0 -4px #c9c2b3}
.jetpack .mark{width:14px;background:linear-gradient(#d8d2c6,#e24a12);border-radius:3px}
.raygun .mark{width:26px;height:10px;background:#c9c2b3;border-radius:3px 10px 10px 3px;margin-top:6px}
.valve .mark{width:12px;height:20px;background:linear-gradient(#fbe7a0,#c87830);border-radius:6px 6px 2px 2px}
.scope .mark{width:26px;height:8px;background:#8a8478;border-radius:4px;margin-top:8px}
.geiger .mark{width:16px;height:20px;background:#3a2a18;border:2px solid #e8c36a}
.dial .mark{border:3px solid #e24a12;border-radius:50%;background:#1b140c}
.punch .mark{width:20px;height:14px;background:#e8dcc0;margin-top:4px;box-shadow:inset 2px 2px 0 #1b140c,inset 10px 6px 0 #1b140c}
.rivet .mark{width:16px;height:16px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#f2eee4,#8a867c 55%,#3a3834);margin-top:3px}
.aerial .mark{width:2px;height:20px;background:#c9c2b3;box-shadow:-6px 8px 0 0 #c9c2b3,6px 8px 0 0 #c9c2b3}
.has-art .cell-face{background-color:#000;box-shadow:none}
.has-art .cell-face .fb{display:none}
.msg{margin:6px 18px 0;height:28px;background:#120e0a;border:1px solid #8a6a20;color:#f0a030;font-size:11px;letter-spacing:.12em;
display:flex;align-items:center;justify-content:center;text-transform:uppercase;overflow:hidden;white-space:nowrap}
.pays{margin:6px 18px 0;height:118px;background:linear-gradient(180deg,#2b2116,#160f0a);border:1px solid #8a6a20;border-radius:3px;
display:grid;grid-template-columns:repeat(4,1fr);gap:2px;padding:4px}
.pay{display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:8px;letter-spacing:.04em;color:#d9c48a;line-height:1.15;text-align:center}
.pay b{color:#f3e6c4;font-size:9px}
.pay .mini{width:18px;height:18px;margin-bottom:1px;background:#000;border-radius:2px;overflow:hidden}
.console{margin:8px 18px 0;display:flex;align-items:center;justify-content:space-between;gap:10px}
.meter{flex:1;background:linear-gradient(180deg,#1a120c,#0a0705);border:2px solid #c9a227;border-radius:4px;padding:4px 8px}
.meter label{display:block;font-size:8px;letter-spacing:.2em;color:#c9a227}
.meter .n{font-size:22px;color:#f0a030;letter-spacing:.08em;text-shadow:0 0 8px #f0a03066;font-family:"Courier New",Courier,monospace}
#play{width:86px;height:86px;border-radius:50%;border:6px solid #d4cfc4;cursor:pointer;color:#2a1008;letter-spacing:.12em;font-weight:700;
font-family:Palatino,"Times New Roman",serif;font-size:15px;
background:radial-gradient(circle at 35% 30%,#ff9a4a,#c43800 54%,#5a1200);
box-shadow:0 7px 0 #4a1808,0 12px 16px #0008,inset 0 2px 0 #ffd0a8}
#play:active{transform:translateY(4px);box-shadow:0 3px 0 #4a1808,0 6px 10px #0008}
#play:disabled{filter:grayscale(.25) brightness(.8);cursor:default}
.belly{margin:8px 18px 0;height:54px;background:linear-gradient(180deg,#1a3048,#0b1824);border:1px solid #4a6a80;border-radius:3px;
color:#9ec4d4;font-size:9px;letter-spacing:.08em;display:flex;align-items:center;justify-content:center;text-align:center;padding:0 10px;
background-size:cover;background-position:center}
.mute{position:absolute;right:14px;bottom:10px;width:34px;height:34px;border-radius:50%;border:2px solid #c9c2b3;background:radial-gradient(circle at 40% 35%,#6a6458,#2a2620);
color:#e8c36a;font-size:8px;letter-spacing:.06em;cursor:pointer;z-index:3;font-family:Palatino,serif}
.fx{position:absolute;inset:16px 18px auto 18px;height:248px;pointer-events:none;z-index:4;display:none}
.fx.on{display:block}
.fx .sweep{position:absolute;inset:16px 10px;border-radius:4px;background:conic-gradient(from 0deg,transparent 0 70%,rgba(240,160,48,.28) 85%,transparent 100%);
animation:sw 1.2s linear infinite}
@keyframes sw{to{transform:rotate(360deg)}}
.banner{position:absolute;left:20px;right:20px;top:210px;z-index:5;text-align:center;font-size:18px;letter-spacing:.2em;color:#e24a12;
text-shadow:0 0 12px #e24a12;display:none}
.banner.on{display:block}
#audit{position:absolute;inset:120px 22px 80px;background:#0c0a08ee;border:1px solid #c9a227;color:#e8c36a;padding:16px;display:none;z-index:6;
font-family:"Courier New",Courier,monospace;font-size:13px;line-height:1.7}
#audit h2{margin:0 0 8px;font-family:Palatino,serif;letter-spacing:.16em;font-size:16px;color:#f0a030}
.flash{animation:cabflash .6s linear 3}
@keyframes cabflash{50%{filter:brightness(1.45) saturate(1.3)}}
</style>
</head>
<body>
<div id="stage">
<div id="cab">
<span class="rivet" style="left:8px;top:8px"></span>
<span class="rivet" style="right:8px;top:8px"></span>
<span class="rivet" style="left:8px;bottom:8px"></span>
<span class="rivet" style="right:8px;bottom:8px"></span>
<div class="topper" id="topper">
<div class="topper-art" id="topperArt"></div>
<div class="tubes"><i class="tube"></i><i class="tube"></i><i class="tube"></i></div>
<div class="tubes r"><i class="tube"></i><i class="tube"></i><i class="tube"></i></div>
<h1><span class="a">ATOMIC</span><span class="b">BANDIT</span></h1>
</div>
<div class="plate">WILD SAUCER SUBSTITUTES EXCEPT BEACON · 3 BEACONS LOCK RADAR — COUNTDOWN SPINS AT 2×</div>
<div class="bezel">
<div class="reels" id="reels"></div>
<div class="fx" id="fx"><div class="sweep"></div></div>
</div>
<div class="msg" id="msg">PRESS PLAY — CIVIL DEFENSE DEMONSTRATOR</div>
<div class="banner" id="banner">RADAR LOCK</div>
<div class="pays" id="pays"></div>
<div class="console">
<div class="meter"><label>CREDITS</label><div class="n" id="credits">500</div></div>
<button id="play" type="button">PLAY</button>
<div class="meter"><label>PAID</label><div class="n" id="paid">0</div></div>
</div>
<div class="belly" id="belly">20 LINES · 1 CREDIT A LINE · PUBLIC EXHIBITION MACHINE · NO CASH VALUE</div>
<button class="mute" id="mute" type="button">SND</button>
<div id="audit"></div>
<img id="symsheet" src="art/symbols.png" alt="">
</div>
</div>
<script>
(function(){
var IDS=["saucer","beacon","rocket","robot","helmet","atom","orbiter","jetpack","raygun","valve","scope","geiger","dial","punch","rivet","aerial"];
var NAMES=["X-Craft","Beacon","Moon Rocket","Automaton","Bowl Helm","Uranium Daisy","Orbiter","Rocket Belt","Atom Pistol","Radio Valve","Refractor","Click Box","Freq Dial","Census Card","Chrome Rivet","Rabbit Ears"];
var WILD="saucer", SCAT="beacon";
var PAYS={
saucer:{3:200,4:800,5:4000},
beacon:{3:2,4:10,5:50},
rocket:{2:50,3:200,4:800,5:2500},
robot:{2:25,3:125,4:500,5:1500},
helmet:{2:25,3:100,4:400,5:1250},
atom:{2:15,3:80,4:300,5:1000},
orbiter:{3:60,4:200,5:600},
jetpack:{3:50,4:150,5:500},
raygun:{3:50,4:150,5:400},
valve:{3:40,4:125,5:300},
scope:{3:25,4:80,5:200},
geiger:{3:25,4:80,5:200},
dial:{3:25,4:60,5:150},
punch:{3:25,4:60,5:150},
rivet:{3:30,4:50,5:125},
aerial:{3:20,4:50,5:125}
};
var REELS=[
["rivet","scope","rocket","geiger","dial","robot","punch","aerial","helmet","rivet","atom","orbiter","jetpack","beacon","raygun","valve","scope","geiger","rocket","dial","punch","robot","aerial","helmet","rivet","atom","orbiter","saucer","jetpack","raygun","valve","scope","geiger","dial","punch","aerial","orbiter","rivet","beacon","jetpack","raygun","valve","scope","geiger","dial","punch","aerial","rivet"],
["scope","rivet","robot","dial","geiger","rocket","aerial","punch","atom","rivet","helmet","jetpack","orbiter","valve","beacon","raygun","geiger","scope","robot","punch","dial","rocket","helmet","aerial","rivet","orbiter","atom","jetpack","saucer","valve","raygun","geiger","scope","punch","dial","aerial","jetpack","beacon","rivet","orbiter","valve","raygun","geiger","scope","punch","dial","aerial","rivet"],
["rivet","geiger","helmet","dial","punch","scope","aerial","orbiter","jetpack","beacon","rivet","geiger","atom","dial","punch","raygun","valve","scope","robot","rivet","geiger","helmet","dial","punch","aerial","jetpack","atom","saucer","raygun","valve","scope","geiger","punch","dial","rivet","aerial","orbiter","jetpack","beacon","raygun","valve","scope","geiger","punch","dial","rivet","aerial","rocket"],
["rivet","dial","punch","geiger","scope","aerial","valve","rivet","dial","punch","geiger","scope","jetpack","aerial","rivet","dial","punch","orbiter","geiger","scope","raygun","aerial","rivet","dial","punch","helmet","geiger","saucer","valve","scope","aerial","rivet","dial","punch","atom","orbiter","jetpack","beacon","raygun","valve","geiger","scope","punch","dial","rivet","aerial","robot","rocket"],
["rivet","punch","dial","scope","geiger","aerial","rivet","punch","dial","valve","scope","geiger","aerial","rivet","punch","dial","orbiter","raygun","scope","geiger","aerial","rivet","punch","dial","jetpack","saucer","scope","geiger","aerial","rivet","punch","dial","valve","atom","orbiter","jetpack","helmet","beacon","raygun","valve","geiger","aerial","punch","rivet","dial","robot","rocket","scope"]
];
var 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],
[1,0,0,0,1],[1,2,2,2,1],[0,0,1,2,2],[2,2,1,0,0],[1,0,1,2,1],
[1,2,1,0,1],[0,1,1,1,2],[2,1,1,1,0],[0,1,0,1,0],[2,1,2,1,2],
[1,1,0,0,1],[1,1,2,2,1],[0,0,0,1,2],[2,2,2,1,0],[0,1,2,2,2]
];
var LINES=20, BET=1, TOTAL=20, credits=500, spinning=false, muted=false, artOk=false, demoNext=false, featureOn=false;
var sheet=document.getElementById("symsheet");
sheet.style.position="absolute";sheet.style.left="-9999px";sheet.style.width="1px";sheet.style.height="1px";
sheet.onload=function(){artOk=true;document.body.classList.add("has-art");paintAll();paintPays();};
sheet.onerror=function(){artOk=false;paintAll();paintPays();};
var stops=REELS.map(function(s,i){return (i*7)%s.length;});
var reelsEl=document.getElementById("reels");
var cells=[];
for(var r=0;r<5;r++){
var col=document.createElement("div");col.className="reel";
var colCells=[];
for(var row=0;row<3;row++){
var c=document.createElement("div");c.className="cell";
var f=document.createElement("div");f.className="cell-face";
c.appendChild(f);col.appendChild(c);colCells.push(f);
}
reelsEl.appendChild(col);cells.push(colCells);
}
function face(el,id){
var ix=IDS.indexOf(id), rr=(ix/4)|0, cc=ix%4;
el.className="cell-face "+id;
el.parentNode.classList.remove("win");
if(artOk){
el.style.backgroundImage="url("+sheet.src+")";
el.style.backgroundSize="400% 400%";
el.style.backgroundPosition=(cc*33.333)+"% "+(rr*33.333)+"%";
el.innerHTML="";
}else{
el.style.backgroundImage="none";
el.innerHTML='<div class="fb '+id+'"><div class="mark"></div>'+NAMES[ix].split(" ")[0]+"</div>";
}
}
function gridFrom(st){
var g=[[],[],[]];
for(var r=0;r<5;r++){
var s=REELS[r], L=s.length;
for(var row=0;row<3;row++) g[row][r]=s[(st[r]+row)%L];
}
return g;
}
function paintStops(st){
var g=gridFrom(st);
for(var r=0;r<5;r++) for(var row=0;row<3;row++) face(cells[r][row], g[row][r]);
}
function paintAll(){paintStops(stops);}
function paintPays(){
var box=document.getElementById("pays");box.innerHTML="";
IDS.forEach(function(id,ix){
var d=document.createElement("div");d.className="pay";
var m=document.createElement("div");m.className="mini";
if(artOk){
var rr=(ix/4)|0, cc=ix%4;
m.style.backgroundImage="url("+sheet.src+")";
m.style.backgroundSize="400% 400%";
m.style.backgroundPosition=(cc*33.333)+"% "+(rr*33.333)+"%";
}else{
m.innerHTML='<div class="fb '+id+'" style="transform:scale(.7)"><div class="mark"></div></div>';
}
var p=PAYS[id];
var role=id===WILD?"WILD":id===SCAT?"SCAT ×BET":"LINE";
d.appendChild(m);
d.innerHTML+= "<b>"+NAMES[ix].split(" ")[0]+"</b>"+role+"<br>3:"+p[3]+(p[4]?" 4:"+p[4]:"")+(p[5]?" 5:"+p[5]:"");
box.appendChild(d);
});
}
function bestLine(syms){
var best=0, cands=IDS.filter(function(id){return id!==SCAT;});
for(var t=0;t<cands.length;t++){
var target=cands[t], n=0;
for(var i=0;i<5;i++){
var s=syms[i];
if(target===WILD){ if(s===WILD) n++; else break; }
else { if(s===target||s===WILD) n++; else break; }
}
var tab=PAYS[target];
if(tab && tab[n] && tab[n]>best) best=tab[n];
}
return best;
}
function evaluate(st){
var g=gridFrom(st), lineTotal=0, hits=[], sc=0;
for(var li=0;li<PAYLINES.length;li++){
var pl=PAYLINES[li], rowsyms=[];
for(var r=0;r<5;r++) rowsyms.push(g[pl[r]][r]);
var pay=bestLine(rowsyms);
if(pay>0){ lineTotal+=pay*BET; hits.push({li:li,pay:pay,pl:pl}); }
}
for(var row=0;row<3;row++) for(var r=0;r<5;r++) if(g[row][r]===SCAT) sc++;
var scPay=(PAYS.beacon[sc]||0)*TOTAL;
return {grid:g,lineTotal:lineTotal,sc:sc,scPay:scPay,total:lineTotal+scPay,hits:hits};
}
function highlight(res){
for(var r=0;r<5;r++) for(var row=0;row<3;row++) cells[r][row].parentNode.classList.remove("win");
res.hits.forEach(function(h){
for(var r=0;r<5;r++) cells[r][h.pl[r]].parentNode.classList.add("win");
});
if(res.sc>=3){
for(var r=0;r<5;r++) for(var row=0;row<3;row++) if(res.grid[row][r]===SCAT) cells[r][row].parentNode.classList.add("win");
}
}
function setCredits(n){credits=n;document.getElementById("credits").textContent=String(n);}
function setPaid(n){document.getElementById("paid").textContent=String(n);}
function setMsg(t){document.getElementById("msg").textContent=t;}
var actx=null, master=null;
function ensureAudio(){
if(!actx){
var AC=window.AudioContext||window.webkitAudioContext;
actx=new AC();
master=actx.createGain();
master.gain.value=muted?0:0.18;
master.connect(actx.destination);
}
if(actx.state==="suspended") actx.resume();
}
function beep(freq,dur,type,gain,at){
if(!actx||muted) return;
var t=at!=null?at:actx.currentTime;
var o=actx.createOscillator(), g=actx.createGain();
o.type=type||"square"; o.frequency.setValueAtTime(freq,t);
g.gain.setValueAtTime(gain||0.08,t);
g.gain.exponentialRampToValueAtTime(0.001,t+dur);
o.connect(g); g.connect(master);
o.start(t); o.stop(t+dur+0.02);
}
function noise(dur,gain){
if(!actx||muted) return;
var n=actx.createBuffer(1, actx.sampleRate*dur, actx.sampleRate);
var d=n.getChannelData(0);
for(var i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*(1-i/d.length);
var s=actx.createBufferSource(); s.buffer=n;
var f=actx.createBiquadFilter(); f.type="bandpass"; f.frequency.value=1200; f.Q.value=0.6;
var g=actx.createGain(); g.gain.value=gain||0.05;
s.connect(f); f.connect(g); g.connect(master); s.start();
}
function spinHum(){ noise(0.18,0.04); beep(70,0.12,"sawtooth",0.03); }
function stopThunk(i){ beep(90-i*6,0.08,"triangle",0.1); }
function winFanfare(mult){
var notes=mult>=50?[196,247,294,392,494,587]:mult>=20?[220,277,330,440,554]:mult>=5?[247,311,370,494]:[330,392,494];
notes.forEach(function(n,i){ beep(n,0.16+Math.min(mult,40)*0.004, i%2?"square":"triangle", 0.07, actx.currentTime+i*0.09); });
if(mult>=20) noise(0.4,0.06);
}
function coinTick(){ beep(1400,0.04,"square",0.04); }
function radarPing(){ beep(880,0.12,"sine",0.07); setTimeout(function(){beep(440,0.18,"sine",0.06);},120); }
function featureTone(){ [523,494,440,392,330].forEach(function(n,i){ beep(n,0.22,"triangle",0.08, actx.currentTime+i*0.16); }); }
function randStop(i){ return (Math.random()*REELS[i].length)|0; }
function demoStops(){
return REELS.map(function(s){ var i=s.indexOf("rocket"); return (i-1+s.length)%s.length; });
}
function countScOnReel(ri, stop){
var s=REELS[ri], n=0;
for(var k=0;k<3;k++) if(s[(stop+k)%s.length]===SCAT) n++;
return n;
}
function celebrate(res, mult){
var x=res.total*(mult||1);
var tb=TOTAL, m=x/tb;
setPaid(x);
if(x<=0){ setMsg(res.sc===2?"TWO BEACONS — RADAR HUNTING":"NO PAY — SPIN AGAIN"); return; }
winFanfare(m);
var cab=document.getElementById("cab");
if(m>=50){ cab.classList.add("flash"); setTimeout(function(){cab.classList.remove("flash");},2000); setMsg("ATOMIC PAY "+x); }
else if(m>=20){ setMsg("RADAR SWEEP PAYS "+x); }
else if(m>=5){ setMsg("TUBES SURGE "+x); }
else { setMsg("METER CLICK "+x); }
var hops=Math.min(24, 4+Math.floor(m));
var i=0, start=credits-x, step=Math.max(1, (x/hops)|0);
(function tick(){
i++;
var shown=Math.min(credits, start+step*i);
document.getElementById("credits").textContent=String(shown);
if(!muted) coinTick();
if(shown<credits) setTimeout(tick, 40);
})();
}
function runFeature(sc, done){
featureOn=true;
var extra=sc>=5?15:sc>=4?8:5;
document.getElementById("fx").classList.add("on");
document.getElementById("banner").classList.add("on");
document.getElementById("banner").textContent="RADAR LOCK";
featureTone();
setMsg("COUNTDOWN — "+extra+" SPINS AT 2×");
var left=extra, bag=0;
function one(){
if(left<=0){
document.getElementById("fx").classList.remove("on");
document.getElementById("banner").classList.remove("on");
featureOn=false;
if(bag>0){ setCredits(credits+bag); setPaid(bag); setMsg("COUNTDOWN COMPLETE "+bag); winFanfare(bag/TOTAL); }
else setMsg("COUNTDOWN COMPLETE");
done();
return;
}
left--;
document.getElementById("banner").textContent="T-MINUS "+(left+1);
var st=REELS.map(function(_,i){return randStop(i);});
animateTo(st, function(){
var res=evaluate(st);
var win=res.total*2;
bag+=win;
highlight(res);
setPaid(bag);
setMsg((left?"COUNTDOWN "+left+" LEFT — ":"COUNTDOWN LAST — ")+(win?("2× PAY "+win):"NO PAY"));
if(win&&!muted) beep(520,0.1,"square",0.06);
setTimeout(one, win?700:380);
}, true);
}
setTimeout(one, 900);
}
function animateTo(target, cb, fast){
var pos=stops.slice();
var start=performance.now();
var base=fast?420:980;
var delays=[0,90,180,280,420];
var done=0;
function frame(now){
for(var r=0;r<5;r++){
var t=now-start-delays[r];
if(t<0) continue;
var dur=base+r*(fast?40:70);
if(t>=dur){
if(pos[r]!==target[r] || cells[r][0].dataset.locked!=="1"){
pos[r]=target[r];
for(var row=0;row<3;row++) face(cells[r][row], REELS[r][(pos[r]+row)%REELS[r].length]);
cells[r][0].dataset.locked="1";
stopThunk(r);
done++;
if(r===3 && !fast){
var sc=0;
for(var k=0;k<4;k++) sc+=countScOnReel(k, pos[k]);
if(sc>=2){ radarPing(); setMsg("BEACONS LOCKED — HUNTING FIFTH"); }
}
}
}else{
var spinPos=(pos[r]+((t/16)|0))%REELS[r].length;
for(var row=0;row<3;row++) face(cells[r][row], REELS[r][(spinPos+row)%REELS[r].length]);
}
}
if(done<5) requestAnimationFrame(frame);
else { stops=target.slice(); cells.forEach(function(col){col[0].dataset.locked="0";}); cb(); }
}
cells.forEach(function(col){col[0].dataset.locked="0";});
if(!fast) spinHum();
requestAnimationFrame(frame);
}
function play(){
if(spinning||featureOn) return;
ensureAudio();
if(credits<TOTAL){
setCredits(credits+500);
setMsg("ATOMIC ENERGY COMMISSION GRANT +500");
beep(300,0.2,"triangle",0.08);
return;
}
spinning=true;
document.getElementById("play").disabled=true;
setCredits(credits-TOTAL);
setPaid(0);
setMsg("REELS IN MOTION");
var st=demoNext?demoStops():REELS.map(function(_,i){return randStop(i);});
demoNext=false;
if(!muted) { spinHum(); var hum=setInterval(function(){ if(!spinning) clearInterval(hum); else spinHum(); },180); }
animateTo(st, function(){
var res=evaluate(st);
setCredits(credits+res.total);
highlight(res);
celebrate(res,1);
if(res.sc>=3){
runFeature(res.sc, function(){
spinning=false;
document.getElementById("play").disabled=false;
});
}else{
spinning=false;
document.getElementById("play").disabled=false;
}
}, false);
}
function fit(){
var s=Math.min(window.innerWidth/420, window.innerHeight/760);
document.getElementById("cab").style.transform="translate(-50%,-50%) scale("+s+")";
}
window.addEventListener("resize",fit);
fit();
paintPays();
paintAll();
function tryDeco(id, el){
var im=new Image();
im.onload=function(){ el.style.backgroundImage="url(art/"+id+".png)"; };
im.src="art/"+id+".png";
}
tryDeco("topper", document.getElementById("topperArt"));
tryDeco("belly", document.getElementById("belly"));
tryDeco("backdrop", document.body);
document.getElementById("play").addEventListener("click",play);
document.getElementById("mute").addEventListener("click",function(){
muted=!muted;
this.textContent=muted?"OFF":"SND";
ensureAudio();
if(master) master.gain.value=muted?0:0.18;
});
function runAudit(n){
document.getElementById("audit").style.display="block";
var wagered=0, won=0, hits=0, big=0, sc3=0;
for(var i=0;i<n;i++){
var st=REELS.map(function(_,ri){return randStop(ri);});
var res=evaluate(st);
wagered+=TOTAL; won+=res.total;
if(res.total>0) hits++;
if(res.total>big) big=res.total;
if(res.sc>=3) sc3++;
}
var rtp=wagered?(100*won/wagered):0;
var hf=n?(100*hits/n):0;
document.getElementById("audit").innerHTML="<h2>RANGE AUDIT</h2>SPINS "+n+"<br>WAGERED "+wagered+"<br>WON "+won+"<br>RTP "+rtp.toFixed(2)+"%<br>HIT FREQUENCY "+hf.toFixed(2)+"%<br>BIGGEST SINGLE WIN "+big+"<br>SPINS WITH 3+ BEACONS "+sc3;
}
function readHash(){
var h=location.hash||"";
if(h.indexOf("audit=")>0 || h.indexOf("#audit=")==0){
var num=parseInt(h.split("audit=")[1],10);
if(!num) num=50000;
runAudit(num);
}
if(h==="#demo") demoNext=true;
}
window.addEventListener("hashchange",readHash);
readHash();
})();
</script>
</body>
</html>
{
"machine": { "name": "ATOMIC BANDIT", "credits": 500, "betPerLine": 1, "lines": 20 },
"sheet": {
"style": "Hand-painted 1954 pulp-magazine gouache on board, airbrushed chrome, visible halftone dots, bakelite blacks, atomic-orange enamel, vacuum-tube amber, deep Prussian-blue space, ruler-and-compass outlines, optimistic industrial toy scale, no neon, no LEDs, no modern UI, one object centered on flat black, identical 512-pixel maquette scale.",
"cells": [
{ "cell": 0, "id": "saucer", "name": "X-Craft", "role": "wild", "prompt": "A 1954 World's Fair toy flying saucer: thick chrome doughnut hull, ring of square portholes, clear bubble canopy with two tiny pilots in knit caps, three ball-and-stick landing pads.", "pays": { "3": 200, "4": 800, "5": 4000 } },
{ "cell": 1, "id": "beacon", "name": "Civil Defense Beacon", "role": "scatter", "prompt": "A striped civil-defense tower topped by a small spinning mesh radar dish, amber warning lamp, riveted lattice mast, 1950s municipal hardware.", "pays": { "3": 2, "4": 10, "5": 50 } },
{ "cell": 2, "id": "rocket", "name": "Moon Rocket", "role": "pay", "prompt": "A silver V-2 descendant moon rocket with black-and-white checker tail fins, needle nose, tiny porthole band, standing upright like a desk trophy.", "pays": { "2": 50, "3": 200, "4": 800, "5": 2500 } },
{ "cell": 3, "id": "robot", "name": "Domestic Automaton", "role": "pay", "prompt": "A boxy smiling 1950s household robot, visor slit, cocktail-tray arms, chest dials, tank-tread feet, chrome and cream enamel.", "pays": { "2": 25, "3": 125, "4": 500, "5": 1500 } },
{ "cell": 4, "id": "helmet", "name": "Stratosphere Helmet", "role": "pay", "prompt": "A goldfish-bowl space helmet with leather collar, brass radio pack, twin hose nipples, reflected studio lights on the glass.", "pays": { "2": 25, "3": 100, "4": 400, "5": 1250 } },
{ "cell": 5, "id": "atom", "name": "Uranium Daisy", "role": "pay", "prompt": "A Bohr atom model like a chrome desk toy: berry nucleus, three tilted electron rings, tiny orange electron beads.", "pays": { "2": 15, "3": 80, "4": 300, "5": 1000 } },
{ "cell": 6, "id": "orbiter", "name": "Orbiter", "role": "pay", "prompt": "A pre-satellite spiked orbital sphere: polished metal ball with six slender antennas and a painted USAF star roundel.", "pays": { "3": 60, "4": 200, "5": 600 } },
{ "cell": 7, "id": "jetpack", "name": "Rocket Belt", "role": "pay", "prompt": "A personal rocket belt: twin chrome bottles, leather shoulder straps, orange exhaust petals, buckles and pressure gauges.", "pays": { "3": 50, "4": 150, "5": 500 } },
{ "cell": 8, "id": "raygun", "name": "Atomic Pistol", "role": "pay", "prompt": "A finned chrome atomic pistol with a glass capacitor bulb on the barrel, bakelite grip, no beam, no muzzle flash.", "pays": { "3": 50, "4": 150, "5": 400 } },
{ "cell": 9, "id": "valve", "name": "Radio Valve", "role": "pay", "prompt": "A glowing glass radio valve / vacuum tube, visible mica plates and wires, amber internal glow, keyed octal base.", "pays": { "3": 40, "4": 125, "5": 300 } },
{ "cell": 10, "id": "scope", "name": "Refractor", "role": "pay", "prompt": "A short brass observatory refractor telescope on a tiny fork mount, black dew cap, engraved focus ring.", "pays": { "3": 25, "4": 80, "5": 200 } },
{ "cell": 11, "id": "geiger", "name": "Click Box", "role": "pay", "prompt": "A portable 1950s geiger counter lunchbox: wrinkle-black case, analog meter, probe wand on a coiled cord, yellow trefoil decal.", "pays": { "3": 25, "4": 80, "5": 200 } },
{ "cell": 12, "id": "dial", "name": "Frequency Dial", "role": "pay", "prompt": "A large bakelite radio frequency knob, cream numerals 0-100, chrome pointer, wood-grain panel fragment behind it.", "pays": { "3": 25, "4": 60, "5": 150 } },
{ "cell": 13, "id": "punch", "name": "Census Card", "role": "pay", "prompt": "A manila IBM-style punch card with a tidy field of rectangular holes and the printed heading SPACE CENSUS 1954.", "pays": { "3": 25, "4": 60, "5": 150 } },
{ "cell": 14, "id": "rivet", "name": "Chrome Rivet", "role": "pay", "prompt": "A single oversized aircraft rivet, mirror chrome dome, dark steel collar, hard studio highlight.", "pays": { "3": 30, "4": 50, "5": 125 } },
{ "cell": 15, "id": "aerial", "name": "Rabbit Ears", "role": "pay", "prompt": "A rabbit-ear television aerial on a bakelite disk base, two telescoping chrome wands spread in a V, cloth-wrapped lead.", "pays": { "3": 20, "4": 50, "5": 125 } }
]
},
"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],
[1,0,0,0,1],
[1,2,2,2,1],
[0,0,1,2,2],
[2,2,1,0,0],
[1,0,1,2,1],
[1,2,1,0,1],
[0,1,1,1,2],
[2,1,1,1,0],
[0,1,0,1,0],
[2,1,2,1,2],
[1,1,0,0,1],
[1,1,2,2,1],
[0,0,0,1,2],
[2,2,2,1,0],
[0,1,2,2,2]
],
"reels": [
["rivet","scope","rocket","geiger","dial","robot","punch","aerial","helmet","rivet","atom","orbiter","jetpack","beacon","raygun","valve","scope","geiger","rocket","dial","punch","robot","aerial","helmet","rivet","atom","orbiter","saucer","jetpack","raygun","valve","scope","geiger","dial","punch","aerial","orbiter","rivet","beacon","jetpack","raygun","valve","scope","geiger","dial","punch","aerial","rivet"],
["scope","rivet","robot","dial","geiger","rocket","aerial","punch","atom","rivet","helmet","jetpack","orbiter","valve","beacon","raygun","geiger","scope","robot","punch","dial","rocket","helmet","aerial","rivet","orbiter","atom","jetpack","saucer","valve","raygun","geiger","scope","punch","dial","aerial","jetpack","beacon","rivet","orbiter","valve","raygun","geiger","scope","punch","dial","aerial","rivet"],
["rivet","geiger","helmet","dial","punch","scope","aerial","orbiter","jetpack","beacon","rivet","geiger","atom","dial","punch","raygun","valve","scope","robot","rivet","geiger","helmet","dial","punch","aerial","jetpack","atom","saucer","raygun","valve","scope","geiger","punch","dial","rivet","aerial","orbiter","jetpack","beacon","raygun","valve","scope","geiger","punch","dial","rivet","aerial","rocket"],
["rivet","dial","punch","geiger","scope","aerial","valve","rivet","dial","punch","geiger","scope","jetpack","aerial","rivet","dial","punch","orbiter","geiger","scope","raygun","aerial","rivet","dial","punch","helmet","geiger","saucer","valve","scope","aerial","rivet","dial","punch","atom","orbiter","jetpack","beacon","raygun","valve","geiger","scope","punch","dial","rivet","aerial","robot","rocket"],
["rivet","punch","dial","scope","geiger","aerial","rivet","punch","dial","valve","scope","geiger","aerial","rivet","punch","dial","orbiter","raygun","scope","geiger","aerial","rivet","punch","dial","jetpack","saucer","scope","geiger","aerial","rivet","punch","dial","valve","atom","orbiter","jetpack","helmet","beacon","raygun","valve","geiger","aerial","punch","rivet","dial","robot","rocket","scope"]
],
"math": { "claimedRtpBase": 94.2, "claimedHitFrequency": 24.8, "claimedFeatureRtp": 7.8, "volatility": "medium" },
"cabinet": [
{ "id": "topper", "aspect": "16:9", "prompt": "A 1954 enamel carnival topper painting: chrome letters ATOMIC BANDIT over a night World's Fair sky, a saucer and a checker-fin rocket, orange searchlights, halftone dots, no neon." },
{ "id": "belly", "aspect": "16:9", "prompt": "Belly glass for a 1950s slot: cutaway of an atomic pile and a radar map of the Moon, cream and orange inks on deep blue, riveted caption plate COUNTDOWN SPINS." },
{ "id": "backdrop", "aspect": "9:16", "prompt": "Portrait backdrop behind the cabinet: dark exhibition hall, distant geodesic dome, concentric radar circles on the floor, warm tungsten spots, 1954 color film." }
]
}
The two-beacon radar crawl. After two Civil Defense Beacons lock in the window, the last reels drop to a hunting crawl and the cabinet ticks a sonar ping on every symbol that slides past; most crawls miss, a hit starts Countdown at 2×, and the small 2-oak rocket/robot pays keep the credit meter twitching so the next hunt is always one mushroom-button press away.