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.jsis the only thing that talks to the platform (viapostMessage).
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.
- Drop the files into
games/<slug>/(slug = lowercase-dashes, e.g.music-quiz). - Write
funday-plugin.json—integrationType: "iframe-themeable",entryPoint: "index.html", nestedmetadata(title, description, genre, min/maxPlayers, thumbnail). See below. - Add a thumbnail (
thumbnail.png) — the manifest validator requires the file to exist. - 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. - 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. - Add
funday-bridge.jsand<script src="funday-bridge.js">toindex.htmlto 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.
| When | Message | Why |
|---|---|---|
| Host says hello | host → funday:handshake | platform announces itself |
| We answer | game → funday:ack + game:ready | stops the handshake, says “loaded” |
| Each question | game → funday:nav:set | top-bar HUD: Question 3/10 · Score 1200 |
| On load | game → funday:dock:set | dock buttons: Restart / Settings / Fullscreen |
| Game over | game → funday:score-submitted | writes to the music-quiz_high_score leaderboard |
| Host theme | host → funday:theme-inject | we mirror it onto data-theme (light/dark) |
| Host pause | host → funday:pause / funday:resume | we 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
| # | Change | Why |
|---|---|---|
| 1 | game-core.js: new WebsimSocket() → benign {clientId, peers} fallback | websim’s class doesn’t exist here; the original crashed single-player on the very first line |
| 2 | websim-multiplayer.js: early-return when WebsimSocket is missing | clean “multiplayer off” instead of a thrown error |
| 3 | Arena (multiplayer) button hidden by the bridge | no multiplayer backend yet — see below |
| 4 | DaisyUI + Google Fonts + Feather icons vendored into vendor/ | self-contained, no external CDN at runtime |
| 5 | Added data-theme="dark" to <html> | the game never set it → DaisyUI surfaces were invisible (transparent) |
| 6 | Added funday-bridge.js | platform integration |
Everything else (scoring, timer, jokers, animations, themes, the OpenTDB question API) is the original game, untouched.
File map
| File / dir | What it is |
|---|---|
index.html | entry — importmap + vendored deps + bridge |
funday-bridge.js | Funday-added — the bridge + game glue |
funday-plugin.json | the manifest Funday reads |
thumbnail.png | library card image |
vendor/ | local DaisyUI / fonts / Feather (no external calls) |
question-api.js | OpenTDB fetch + offline fallback bank |
game-core.js, game-modes.js, game-ui.js | engine / modes / DOM |
scoring-system.js, timer-system.js, joker-*.js | scoring, timer, lifelines |
multiplayer*.js, websim-multiplayer.js, reactions.js | disabled 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-quizNo 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:
- Add
games/music-quiz/server/match_handler.ts(relay presence + question/room state). - Register it in
nakama-modules/index.tsand map it ingames/_platform/server/game_registry.ts. - Re-point
websim-multiplayer.jsonto the bridge match API (funday:request-match/funday:send-match-state). - 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 toposition:relative; z-index:0— for the capture only, never in the shipped file. - Third-party APIs are allowed; platform URLs are not. Calling
opentdb.comis fine; hardcodingfunday.gg/nakama.funday.ggfails 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).