MusicQuiz 🎵#

Status: ✅ Live (single-player) · Version: 1.0.0 Path: games/music-quiz/ · Type: iframe-themeable · Players: 1 Live: funday.gg/play/music-quiz

A fast trivia quiz: 10 questions, beat-the-clock scoring, lifelines (“jokers”), streaks. Questions come from the free Open Trivia DB. It started life as a websim project and was ported to run cleanly on Funday.


How it works in 10 seconds#

Funday play page  ──iframe──►  /games/assets/music-quiz/?embed=1
        ▲                              │
        │   postMessage (the bridge)   │
        └──────────────────────────────┘
   theme, pause/resume            ack, ready, HUD, score
  • It’s just a folder of HTML/JS/CSS in games/music-quiz/.
  • Funday serves it at runtime from that folder — no build step, no restart.
  • funday-bridge.js is the only thing that talks to the platform (via postMessage).

The Recipe 🍳 (repeat this for ANY websim game)#

A websim export is just a self-contained web app. Turning it into a Funday game is 6 boring steps. No backend needed for a single-player game.

  1. Drop the files into games/<slug>/ (slug = lowercase-dashes, e.g. music-quiz).
  2. Write funday-plugin.jsonintegrationType: "iframe-themeable", entryPoint: "index.html", nested metadata (title, description, genre, min/maxPlayers, thumbnail). See below.
  3. Add a thumbnail (thumbnail.png) — the manifest validator requires the file to exist.
  4. Rip out websim. Anything using WebsimSocket, window.websim, websim rooms/AI must be removed or made safe — it does not exist on Funday and will crash the game otherwise.
  5. Vendor external CDNs into a local vendor/ folder (DaisyUI, fonts, icons). A Funday game should make zero off-platform network calls for its own assets.
  6. Add funday-bridge.js and <script src="funday-bridge.js"> to index.html to talk to the host.

Then verify (2 commands) and it’s live. That’s it.


The Bridge 🌉 (funday-bridge.js)#

The bridge is a small, self-contained script. It speaks FundayBridge v1 over postMessage and is the only allowed channel between a game and the platform. No platform imports, ever.

WhenMessageWhy
Host says hellohost → funday:handshakeplatform announces itself
We answergame → funday:ack + game:readystops the handshake, says “loaded”
Each questiongame → funday:nav:settop-bar HUD: Question 3/10 · Score 1200
On loadgame → funday:dock:setdock buttons: Restart / Settings / Fullscreen
Game overgame → funday:score-submittedwrites to the music-quiz_high_score leaderboard
Host themehost → funday:theme-injectwe mirror it onto data-theme (light/dark)
Host pausehost → funday:pause / funday:resumewe pause/resume the question timer

ELI5: the game and the platform are in two different windows. They can’t call each other’s functions — they can only mail letters (postMessage). The bridge is the mailroom.


What we changed from the websim original#

#ChangeWhy
1game-core.js: new WebsimSocket() → benign {clientId, peers} fallbackwebsim’s class doesn’t exist here; the original crashed single-player on the very first line
2websim-multiplayer.js: early-return when WebsimSocket is missingclean “multiplayer off” instead of a thrown error
3Arena (multiplayer) button hidden by the bridgeno multiplayer backend yet — see below
4DaisyUI + Google Fonts + Feather icons vendored into vendor/self-contained, no external CDN at runtime
5Added data-theme="dark" to <html>the game never set it → DaisyUI surfaces were invisible (transparent)
6Added funday-bridge.jsplatform integration

Everything else (scoring, timer, jokers, animations, themes, the OpenTDB question API) is the original game, untouched.


File map#

File / dirWhat it is
index.htmlentry — importmap + vendored deps + bridge
funday-bridge.jsFunday-added — the bridge + game glue
funday-plugin.jsonthe manifest Funday reads
thumbnail.pnglibrary card image
vendor/local DaisyUI / fonts / Feather (no external calls)
question-api.jsOpenTDB fetch + offline fallback bank
game-core.js, game-modes.js, game-ui.jsengine / modes / DOM
scoring-system.js, timer-system.js, joker-*.jsscoring, timer, lifelines
multiplayer*.js, websim-multiplayer.js, reactions.jsdisabled MP layer (kept for phase 2)

Manifest essentials (funday-plugin.json)#

{
  "id": "music-quiz",
  "integrationType": "iframe-themeable",
  "entryPoint": "index.html",
  "status": "available",
  "metadata": { "title": "MusicQuiz", "minPlayers": 1, "maxPlayers": 1, "thumbnail": "thumbnail.png" },
  "leaderboards": { "default": "music-quiz_high_score", "configs": {
    "music-quiz_high_score": { "type": "high_score", "sortOrder": "desc", "operator": "best" } } }
}

Run & verify ✅#

# from funday/
node scripts/validate-game-manifest.mjs music-quiz   # → VALID
cd frontend && node ../scripts/check-game-boundaries.mjs   # → no violations for music-quiz

No build or restart needed: iframe-themeable games are served at runtime from GAME_PLUGINS_DIR and the plugin list is re-scanned on every request. Edit a file → reload the page.

Verified 2026-06-09: boots with 0 page errors, real OpenTDB questions load, answering scores and advances all 10 questions, bridge handshake + theme + score-submitted confirmed end-to-end.


Multiplayer (phase 2 — not shipped)#

The websim version had a live “Arena” mode built on websim rooms. Funday has no equivalent yet, so it’s hidden, not faked. To enable it later:

  1. Add games/music-quiz/server/match_handler.ts (relay presence + question/room state).
  2. Register it in nakama-modules/index.ts and map it in games/_platform/server/game_registry.ts.
  3. Re-point websim-multiplayer.js onto the bridge match API (funday:request-match / funday:send-match-state).
  4. Un-hide the Arena button.

Gotchas (learned the hard way) ⚠️#

  • DaisyUI needs data-theme. With none set, every surface uses undefined color vars and renders transparent → the whole game looks blank. Always set <html data-theme="...">.
  • Headless screenshots can lie. The game’s background is position:fixed; z-index:-1. Real browsers render it fine; headless Chromium (SwiftShader) drops everything inside that layer, so captures look blank. To screenshot in headless, override it to position:relative; z-index:0for the capture only, never in the shipped file.
  • Third-party APIs are allowed; platform URLs are not. Calling opentdb.com is fine; hardcoding funday.gg / nakama.funday.gg fails the boundary check.

Credits#

Original MusicQuiz by rebeljam (built on websim). Questions: Open Trivia DB (CC BY-SA 4.0). Feather icons (MIT), DaisyUI (MIT).