Under the hood
Ten games, one leaderboard, zero dependencies. No framework, no build step, no bundler, no tracking pixel, no cookie banner. What you see in view-source is the entire machine. These are the engineering notes.
The philosophy
- Vanilla everything. The whole arcade is hand-written HTML, CSS and JavaScript. If a game needs physics, we write the physics. If it needs charts, we draw the charts.
- View-source is a feature. Every file is commented like someone will read it — because the target audience for this page will.
- Deterministic where it matters. Markets and datasets run on seeded RNG, so they're reproducible, testable, and fair.
- Graceful degradation. If the leaderboard backend is down, every game still works and personal bests still save locally.
Performance budget
A game page loads, first visit, roughly:
| Asset | Size | Notes |
|---|---|---|
| HTML page | 4–11 KB | Semantic, no div soup |
| style.css (shared) | 22 KB | One stylesheet for the whole site |
| Game JS | 7–14 KB | Unminified, commented |
| Shared systems (seed, audio, achievements, stats, share) | 22 KB | One download, cached after the first page |
| Fonts (3× woff2) | 52 KB | Self-hosted, cached after first page |
| Total | ~100 KB | Zero requests to third parties |
No framework means no 200 KB runtime before the first pixel. The heaviest thing on this site is the fonts, and they're optional.
Budget ceilings (flagship)
- Games: 2 MB ceiling per game page. The flagship Warehouse Siege ships ~150 KB total (JS + SFX + canvas art) — a game page must never approach this ceiling; it exists as a hard guardrail, not a target.
- Guide / content pages: 3 MB ceiling. Guide artwork lazy-loads below the fold; the hero image is the only above-the-fold art.
- First paint: fast on mid-range Android hardware over 4G — no render-blocking JS, static canvas pre-rendered, zero third-party requests, always.
- Ceilings are not targets. Every kilobyte over ~100 KB needs a reason written down.
Architecture
Adding game 11 is a one-line manifest entry plus two new files. The nav, footers, sitemap and debug API pick it up automatically — no other file needs editing.
The interesting bits, per game
Stack the Deal — slab slicing
The whole physics model is one pure function. Given the slab below and the dropped slab as centre-x + width, the overlap is the new slab — a clean miss is null:
Eneloop Exchange — a seeded market
The 90-candle day is geometric Brownian motion with regime shifts and scheduled event shocks, built up front from a mulberry32 seed derived from the calendar date. Same date, same chart, for every player on Earth:
Because the series is deterministic per seed, the leaderboard compares trading skill, not luck — and the market is unit-testable in Node.
Sockpuppet Sniffer — synthetic data with ground truth
The ten suspects are generated, not written: sockpuppets get lockstep burst hours shared with their burst group, template-parrot comment similarity (0.87–0.99), and young hyperactive accounts. The briefing teaches exactly the heuristics the generator is tuned against, and a reference classifier ships in the sim core:
Players are scored with the same confusion matrix as a real classifier evaluation: precision, recall, F1.
Warehouse Siege — a real-time tower defense
Six tower archetypes (slow / damage / stun / splash / taunt / aura), three upgrade tiers, seven enemy types with armor and aura mechanics, an economy with wave bonuses and 5% interest, and 20 waves of deterministic composition — same ?seed=, same siege:
The sim core is pure and fully unit-tested in Node: wave determinism, HP scaling, armor halves price-tower damage on clippers, Karen auras slow nearby towers, the honeypot diverts shoppers off-path. The DOM layer is just rendering and input — 1×/2×/3× speed, pause on tab-hide, placement ghosts with range previews.
Canvas, done properly
Every canvas game scales for device pixel ratio (crisp on retina, capped at 2× for perf), clamps the frame delta so tab-switching can't teleport physics, and pauses on visibilitychange:
The leaderboard
Scores go to a free Supabase project through the public REST API. The anon key in hs-config.js is public by design — row-level security on the scores table permits only SELECT and INSERT. There is nothing to steal: no accounts, no emails, no sessions, just handles and numbers.
The debug API
Open devtools on any page. PA.help() prints the tour:
| Handle | What it does |
|---|---|
PA.version | Arcade build version |
PA.manifest | The 10-game manifest (slug, title, short, num) |
PA.games[slug].sim | Pure sim core: seeded RNG, market builder, dataset generator, classifiers — everything unit-testable, no DOM required |
PA.resetBests() | Wipe local personal bests |
Seed.fromURL() / Seed.todaySeed() | Challenge-seed parsing; calendar daily seed |
SFX.play(name) / SFX.toggle() | Synthesized sound effects; SFX.sounds lists all 17 |
ACH.unlock(id) / ACH.defs | Achievement engine; the full 29-trophy registry |
ArcadeStats.summary() | Cross-game plays, streaks, per-game bests |
Share.card(opts) | Render a 1200×630 score-card PNG |
Every game's sim core is registered before any DOM code runs, which means the interesting logic can be required and tested in Node with a stubbed document. That's not an accident — it's how the games were verified.
Seeded challenges — same run, anywhere
Every seeded game reads its seed from the URL. ?seed=12345 plays an exact shareable run; ?daily=1 plays today's calendar seed (YYYYMMDD), identical for every player on Earth. The parsing, the mulberry32 PRNG and the link builder live in one module:
After a run, the game-over panel offers Copy challenge link — send it to a mate and you're both trading the same ENLP chart or sniffing the same suspects. Deterministic content turns a single-player page into a fair multiplayer contest with no server.
Synthesized sound — no audio files
Seventeen sound effects, zero audio assets. assets/js/audio.js builds every sound from raw WebAudio oscillators at play time — enveloped square/triangle/sine tones and tiny arpeggios:
The AudioContext is created lazily on the first user gesture (autoplay policy), the mute flag persists in localStorage (pa_mute), and there's a sound toggle in the footer of every page. SFX.sounds lists the registry for the curious.
Achievements, stats and the trophy room
Achievements are event-sourced: game code calls ACH.unlock('stack-ten') at the exact moment it happens. The engine (assets/js/achieve.js) holds the registry (29 trophies across all ten games), persists unlocks as {id: "YYYY-MM-DD"} in localStorage, fires a toast, and plays the unlock arpeggio. Cross-game stats — plays, per-game bests, days played, consecutive-day streaks — live in assets/js/stats.js, recorded by a one-liner at every game-over. The Trophy Room (trophies.html) renders both with zero backend.
Shareable score cards
assets/js/share.js draws a 1200×630 receipt-style PNG on a canvas — score, rank, run stats, a seeded barcode — and triggers a download. No server, no html2canvas, no screenshot service: Share.card({title, score, lines, seed}) is about 60 lines.
Offline-first PWA
sw.js caches every page, script, stylesheet and font on install (cache-first for same-origin GETs, stale cache versioned by a VERSION constant), so the whole arcade works on a plane. manifest.webmanifest makes it installable; the icon is a 540-byte SVG. Total added weight for the entire "advanced features" layer — seeds, synth audio, achievements, stats, share cards, PWA: ~22 KB of JavaScript.
Steal this
The patterns are free to take: manifest-driven nav, seeded daily content, sim-core-before-DOM, graceful-degradation leaderboards, DPR-aware canvas. The jokes are ours. The engineering is yours if you want it.