Pub Quiz Platform
Product Requirements Document
| Status | Draft |
| Scope | Project (cross-cutting) |
| Last updated | 2026-05-13 |
| Source of truth | .claude/context/knowledgebase/ |
Contents
Overview
Overview
Problem statement
Pub quizzes are a popular social format, and software platforms aimed at running them already exist. They tend to fall into one of two camps: feature-light tools that miss the TV-game-show production values quizmasters want, or feature-heavy platforms whose complexity makes set-up and operation a chore. We bring the rich, animated, interactive experience without the complexity, packaged as a subscription service quizmasters can rely on for a polished quiz night that still works on the unreliable internet typical of a real venue.
Solution outline
Four connected apps, one shared C# schema library:
- Designer (Windows, macOS — iPad / Android tablet authoring is Stretch) — author quizzes as ordered slides grouped into rounds, with each slide carrying a Host canvas, a Client canvas, and per-slide host-notes; save
.quizfiles to disk and push them to a Host over the local network. - Host (iPad, Windows, macOS, Android tablet) — receive a
.quizfrom the Designer (or load one from disk), run the live session as a local-network server, render each slide's Host canvas on a connected projector or TV. On Client join, distributes the.quizpackage to the clients so per-slide play has no network round-trips. - Client (iPhone, Android phone, iPad, Android tablet) — each team joins over local Wi-Fi from one shared device and sees each slide's Client canvas, with elements collecting the team's answers and running mini-games as the slide demands.
- Remote (iPhone, Android phone, iPad, Android tablet) — the quizmaster's pocket controller. Pairs with the Host, mirrors the Host display, shows the author's host-notes for the current slide, and sends control messages so the quizmaster can walk the room. MVP ships minimum viable controls (advance / go-back); the rich control commands (trigger reveals, lock/unlock input, extend/skip timer, override scoring, jump-to-slide) land in Alpha.
A slide's elements are instances of object types — pluggable modules that contribute schema, editor surface, runtime behaviour, and protocol extensions. v1 ships a built-in catalogue (text, image, audio, video, multiple-choice/free-text/drawing/buzzer inputs, timer, leaderboard, mini-game) and adding a new built-in object type does not require changing core code. v1 is fully local: no cloud, no account, no internet at quiz time. Cloud-backed authoring (account, library, Designer↔cloud↔Host distribution, bundle-supplied object types) is a stretch goal for a future release. See Architecture for the full picture and Applications for per-app responsibilities.
Goals
- Deliver a polished, reliable pub quiz experience across the supported tablet, phone, and desktop platforms.
- Support rich, interactive slides via a pluggable object-type model — text and media for the question, varied input modalities for the answer, animated reveals and mini-games where the round demands.
- Allow quiz authors to plan, edit, save, and transfer quizzes between their own devices over a local network. (A cloud-backed account/library system is a future stretch goal.)
- Operate entirely over local Wi-Fi at the venue, with no requirement for internet at any point in v1.
- Keep the system architecture future-proof for additions like 3D content and internet-based play.
v1 ships in four phases — MVP, Alpha, Beta, Production. Beyond v1 is a Stretch backlog. Scope and acceptance criteria for each phase are in Phases.
Users
| Role | Device | Authenticated? | Primary actions |
|---|---|---|---|
| Quiz Author | Designer (Windows, macOS) + Host (Windows, macOS, iPad, Android tablet) | No in v1; stretch Yes (cloud account) for cloud-backed authoring | Create, edit, save quizzes locally; transfer to host over LAN; run quiz nights. |
| Quizmaster (live operator) | Host + optional Remote on phone/tablet | No (paired with Host on the LAN) | Run the live session. The Remote app on the quizmaster's phone mirrors the Host display, shows host-notes, and controls the Host (MVP: advance/go-back; Alpha: rich command set). |
| Team (Participants / Quiz Goers) | Client (iPhone, Android phone, iPad, Android tablet) | No (team name only) | One shared device per team. Join host, submit team answers, see scores. |
The Author and Host operator are typically the same person. Each team plays from a single shared Client device; v1 has no per-individual representation within a team. In v1 no app authenticates against anything — Designer→Host transfer is gated by being on the same local network. stretch When cloud-backed authoring is added, both Designer and Host authenticate against the Author's cloud account; the Client remains unauthenticated by design.
Applications
The platform consists of four apps. This page covers each app's responsibilities and supported platforms; for shared schemas, protocols, and the system-wide architecture see Architecture. For each app's numbered behaviors see Functional Requirements. For each app's UI entry-point inventory (menus / dialogs / panels / shortcuts) see UI Surfaces. For the on-disk shape (scenes, prefabs, scripts) inside each Unity project see Per-App Scaffolding. Phase scoping is in Phases.
| App | Phase introduced | Primary platforms |
|---|---|---|
| Designer | MVP | Windows, macOS (Catalyst) — single .NET MAUI Blazor codebase. iPad / Android tablet authoring is Stretch. |
| Host | MVP | iPad, Windows, macOS, Android tablet |
| Client | MVP | iPhone, Android phone, iPad, Android tablet |
| Remote | MVP (minimum viable controls); rich control commands in Alpha | iPhone, Android phone, iPad, Android tablet |
Designer scope in v1: Windows + macOS desktop only, from a single .NET MAUI Blazor Hybrid codebase. iPad and Android tablet authoring are Stretch and ship derived from the web-based Designer codebase. Web authoring itself is also Stretch and reuses the same Razor component library compiled for Blazor WASM.
Designer designer
A content-authoring tool — a single .NET MAUI Blazor Hybrid desktop app for Windows and macOS (Catalyst). Razor / Blazor components form the entire authoring UI — shell, panels, properties inspector, dialogs. Multi-window is real OS-level windowing via the MAUI Window API. Slide preview is a dedicated Quiz.Preview project, built only for WebGL and embedded in BlazorWebView as a <canvas> — same WebGL build serves desktop and (Stretch) the future web tool, ~95-98% pixel fidelity vs native Host (accepted trade-off). iPad / Android tablet authoring is Stretch and ships from the web-based Designer codebase. Architecture and diagram in Designer Shell. Chrome uses the Studio operator-chrome theme — surface tokens, brand-hue ration, and per-component spec live in Design Specification — Studio.
The authoring model is PowerPoint-style slides. A quiz is an ordered list of slides, optionally grouped into rounds. Each slide owns two independent canvases:
- Host canvas — TV/projector-format, fixed virtual 1920×1080, where elements are placed at absolute coordinates.
- Client canvas — phone/tablet-format, responsive, where elements declare anchors / regions / a stack so the layout adapts across phone, tablet, portrait, and landscape.
Plus a per-slide host-notes field (free-form text the author writes for the quizmaster) which renders only on the Remote app during play, never on the Host canvas or any Client.
Authors place elements on each canvas — a text block, an image, a multiple-choice input, a timer, a drawing input, etc. — by dragging from a palette of object types (the v1 built-in catalogue; bundle-supplied types are a stretch goal). Each element is configured through that object type's editor surface in the Razor properties inspector; the embedded Quiz.Preview Unity WebGL canvas shows the live pixel-accurate render of the same state.
Authors attach media (images, audio, video) as resources referenced by element properties, and configure timing and scoring per slide and per round. Authors also bundle team-customisation assets — buzzer jingles (F-DE-28) and premade avatars (F-DE-30) — which teams pick from at join. The Designer ships a small built-in default asset library (pre-licensed jingles + starter avatars) the author can pull from; selections are copied into the .quiz so the package stays self-contained. Stylus input for sketching and annotation (Apple Pencil and equivalents) is a post-v1 stretch goal — v1 is touch-only.
The MVP-target platforms (Windows, macOS) are stood up cross-platform from day one with the single MAUI Blazor codebase. iPad and Android tablet authoring are Stretch.
For v1 the Designer saves quizzes as .quiz files on the local file system. A "Push to Host" UI action discovers Hosts on the local Wi-Fi (via Bonjour/mDNS), lets the author pick one, and pushes the on-disk .quiz to it over the WebSocket. A separate Run from slide action (F-DE-27) launches a local Host process on the same machine starting at the currently-selected slide — used for fast author-side validation without the network discovery step. Cloud-backed authoring — accounts, a quiz library, version history, soft-delete, restore — is a stretch goal for a future release; v1 ships none of it. Authors can still organize their on-disk quizzes with titles, descriptions, and tags inside the package metadata.
Host host
The "TV show" app, runnable on iPad, Windows, macOS, and Android tablets, and typically connected to a projector or TV (HDMI on most platforms; AirPlay or USB-C also available on Apple devices). The Host:
- Loads
.quizfiles from the local file system. For v1 the user transfers them either by file copy or by accepting an incoming push from the Designer over the local network. Cloud-backed download (sign in, browse a library, download) is a stretch goal. - Validates each loaded package against its built-in object-type registry; refuses packages whose declared object types are missing or version-incompatible.
- Runs an in-process WebSocket server that handles three flows: receiving incoming
.quizpackage transfers from the Designer when idle, running live quiz sessions with Clients, and accepting control connections from the Remote app. - Advertises itself on the local network via Bonjour/mDNS so Designers, Clients, and Remotes can discover it.
- When a Client joins a session, eagerly pushes the slide content and Client-side resources the Client will need for the whole quiz, so per-slide play is round-trip-free.
- Advances through the quiz's slides, rendering each slide's Host canvas at the connected display's resolution (the 1920×1080 virtual canvas scales to fit) and dispatching element behaviours — playing media, running animations, driving "big reveals," showing leaderboards. Per-team identity rendering — team photo on leaderboard rows + team callouts (F-HO-26), and per-team buzzer jingle playback on
core.buzzer-inputfirst-press wins (F-HO-27) — lands in Alpha alongside the relevant object types. - Runs in dual-window operator-view mode (F-HO-25) when more than one display is connected: the audience window owns the projector / TV (clean slide canvas, ships the full Showtime palette per the cross-cutting Design Specification), and a second operator window sits on the operator's laptop / iPad screen with a mirror of the audience output, the current slide's host-notes, the session HUD (timer, scores, slide index), and the same nav + control affordances a paired Remote exposes — effectively an on-machine Remote. The operator window's chrome uses the calmer Studio operator-chrome theme — see Design Specification — Studio for tokens, brand-hue ration, and component rules. On a single-display machine the operator surfaces collapse to a summonable overlay.
- Persists session state (current slide pointer, team list, scores, slide-element state where relevant) to disk often enough that a Host crash followed by a relaunch can resume the same session — this is an Alpha-phase v1 requirement, not Stretch.
Client client
A lightweight team app for phones and tablets — iPhone, Android phone, iPad, and Android tablet. One shared device per team. The Client:
- Discovers active hosts on the local Wi-Fi network via Bonjour/mDNS.
- Lets a team enter a team name and join a session. From Alpha, the captain can also take a photo (or pick from the quiz's bundled premade-avatar set) and pick a per-team buzzer jingle (F-CL-14, F-CL-15); both flow to the Host in the join message and drive per-team rendering / audio there.
- Renders the current slide's Client canvas with its responsive layout adapting to the device's form factor, and dispatches per-element behaviours — collecting team input (multiple choice, free text, drawing, gesture), playing inline media, running mini-games where the slide includes one.
- Shows the team's score and standings.
- Persists its team identity locally so it can rejoin a recovered session as the same team without re-entering the team name (introduced alongside crash recovery in Alpha).
Remote remote
Phase: MVP (minimum viable controls); the rich control-command set lands in Alpha.
The quizmaster's pocket controller — a phone (or tablet) the quizmaster carries while walking the room. It runs on iPhone, Android phone, iPad, and Android tablet. The same person who runs the Host operates the Remote; the Remote does not replace the Host's keyboard/click controls but supplements them.
The Remote in MVP:
- Discovers active Hosts on the local Wi-Fi network via Bonjour/mDNS.
- Pairs with one Host at a time. Pairing is gated (manual confirm on the Host the first time a given Remote connects, or QR-code pairing — final UX is a build-plan decision).
- Connects to the paired Host over a WebSocket on a control message family distinct from Designer transfer and Client live-play.
- Renders a live preview of what the Host's main display is showing (a scaled-down mirror of the Host canvas).
- Displays the per-slide host-notes the author wrote in the Designer — hints, answer keys, presentation cues — visible only on the Remote.
- Displays live session state — current scores, timer remaining, slide index.
- Sends core navigation commands to the Host: advance and go-back.
- Continues to function if a Client misbehaves; treats Wi-Fi blips like the Client does (reconnect with the same Remote identity).
Remote chrome uses the Studio operator-chrome theme — controls, host-notes pane, status bar all sit on calm surfaces. The audience-mirror region inside renders Showtime at full saturation. See Design Specification — Studio for tokens and the per-app component spec.
In Alpha the Remote gains the rich control-command set: jump to a specific slide, trigger element reveals (show the leaderboard / show the answer), lock or unlock Client input, extend or skip the timer, override scoring per team. This is F-RE-9 / F-HO-24.
A session can run with zero or one Remote. There is no multi-Remote support in v1 — the Host owns the session, and one paired Remote at a time is enough to walk the room.
Architecture
Architecture Overview
The platform pairs three Unity live-play apps (Host, Client, Remote) with a .NET MAUI Blazor Hybrid Designer for content authoring, plus a fourth Quiz.Preview Unity project that serves only as the Designer's slide-render target (WebGL build, embedded in BlazorWebView). All four apps ship in MVP. The Remote ships in MVP with a minimum viable controls feature set (discovery, pairing, mirror, host-notes, advance/go-back); its rich control command set lands in Alpha. The Designer's authoring UI is built entirely from Razor / Blazor components running on Windows + macOS (Catalyst) + iPad from one C# codebase. See Designer Shell for the architecture and Decisions for the rationale. The Host, Client, and Remote are UGUI-driven for creative freedom (custom shaders, prefab compositions, animations).
For v1 the platform is fully local: the Designer exports .quiz files to disk and sends them to the Host over local Wi-Fi via a UI action; the Host receives, stores them locally, and at session time pushes per-Client content to each Client as they join. A cloud-backed authoring service (account, library, Designer→cloud→Host distribution) is a stretch goal for a future release — see Backend Schema.
Designer ships on Windows + macOS in MVP from one MAUI Blazor codebase, desktop only. iPad and Android tablet authoring are Stretch. Web authoring is also Stretch and reuses the same Razor component library compiled for Blazor WASM. The Host runs on iPad, Windows, macOS, and Android tablets. Clients run on iPhone, Android phones, iPad, and Android tablets. The Remote runs on the same Client-class platforms. Per-app responsibilities are in Applications.
In v1 there is no cloud and no authentication on any app. Designer and Host meet over the local network; Clients never reach beyond the local network either. The cloud-backed authoring path on the right is a future stretch goal, sketched here so the v1 architecture stays compatible with it.
Where to go next
- Tech Stack for engine, library, and tooling choices.
- Networking for the local-Wi-Fi transport and the live-play model.
- Object-Type Architecture for the slide / element / plugin model.
- Decisions for the rationale behind each load-bearing choice.
Status: Draft Last updated: 2026-04-27
Tech Stack
| Component | Choice | Rationale |
|---|---|---|
| Host, Client, Remote | Unity (C#) | Single engine across the three live-play apps; native 2D and 3D from day one; broad platform reach including iPad / Android tablet; mature animation, particle, and shader tooling. All three apps ship in MVP; the Remote ships with minimum viable controls in MVP and gains its rich control command set in Alpha. |
| Designer shell | .NET MAUI Blazor Hybrid (.NET 8/9, C#, Razor) | Single C# codebase reaches Windows + macOS (Catalyst) — desktop only in v1. Razor components run unchanged on both targets. Future Blazor WASM web tool (Stretch) reuses the same component library — code-share is the load-bearing reason for the framework pick. iPad / Android tablet authoring is Stretch and ships from the web Designer codebase. Mac via Catalyst (UIKit-on-Mac). See Designer Shell and Decisions. |
| Designer UI | Razor / Blazor components (MudBlazor library) | Tool windows, panels, dialogs, modals, chrome, properties inspector — all Razor components, themeable via MudBlazor's CSS variables. Same component library reused by the future Blazor WASM web tool. |
| Designer multi-window | MAUI Window API |
Real OS-level windows on Windows + macOS Catalyst, each hosting its own BlazorWebView. |
| Designer slide preview | Unity WebGL build in BlazorWebView |
Single preview integration across desktop and the future web tool. ~95%+ pixel fidelity vs native Host (WebGL ≠ D3D11/Metal/Vulkan exactly — accepted trade-off). No platform-specific embedding, no HWND/NSView reparenting, no Unity-as-a-Library. |
| Designer ↔ Preview transport | JS bridge (SendMessage + JS callbacks, in-process) | The preview runs inside the Designer's BlazorWebView; no inter-process IPC. Razor components push slide state via unityInstance.SendMessage; the WebGL build emits ready / rendered / error events back via [DllImport("__Internal")] callbacks into JS. |
| Slide preview Unity project | Quiz.Preview (WebGL-only build target) |
Dedicated Unity project, sibling to Quiz.Host / Quiz.Client / Quiz.Remote. Renders a single .quiz slide from pushed JSON state. No editing UI — pure render target driven by the Razor shell. Shares scenes, prefabs, and rendering setup with Host / Client via com.quiz.shared-assets (and com.quiz.runtime where Unity-aware). Replaces the previous Quiz.Designer Unity project, which is renamed and repurposed for this role. |
| Live-play UI (Host / Client / Remote) | UGUI (Unity's prefab/canvas UI system) | Creative freedom for live-play visuals — element prefabs, custom shaders, particle effects, animation, "big reveals." The Remote uses UGUI for its mirrored Host-canvas preview. |
| Shared logic | C# class library (.NET Standard 2.1) consumed by all four Unity projects via local UPM packages (see Repository Layout) | Quiz schemas, DTOs, scoring rules, and message contracts shared between all four apps. UnityEngine-free, so it also runs in headless test harnesses. |
| Embedded WebSocket server + client (Unity apps) | SimpleWebTransport (James-Frowen, MIT, active 2026) |
Single library for both server (Host) and client (Client / Remote) inside Unity. Bypasses Unity's broken HttpListener.AcceptWebSocketAsync with its own pure-TCP WebSocket impl. Used by Mirror Networking in production. Resolved OQ#5 2026-05-11. |
| WebSocket client (Designer) | Bare System.Net.WebSockets.ClientWebSocket (BCL) |
The Designer is .NET MAUI Blazor Hybrid on .NET 8/9 — full BCL access, no Unity constraints. No third-party library needed on the Designer side. |
| Live-play and control protocol | WebSocket with typed message envelopes (validated against shared C# schemas) | Lightweight, well-supported on all target platforms; bidirectional; works over plain Wi-Fi without infrastructure. Distinct message families for Designer transfer, Client live-play, and Remote control share the same transport. See Networking. |
| Session state persistence (Host) | Local file (JSON, periodic snapshot) on the Host's local app data directory | Crash recovery is an Alpha-phase v1 requirement. Snapshot after every scoring event and every slide advance — small writes, fsync. Format is the same DTOs as the live message envelopes, so deserialise == replay. |
| Service discovery | Makaretu.Dns.Multicast.New (active fork, MIT) |
Single C# library for both Host advertisement and Client / Designer / Remote discovery. Pure C#; native NSNetService (iOS) / NsdManager (Android) bridges held as fallback if Alpha prototype reveals reliability issues. Resolved OQ#6 2026-05-11. |
| Camera capture (Client) | Unity WebCamTexture |
Single cross-platform Unity API for team-photo capture at join (F-CL-14). Covers our minimum: capture frame → centre-crop → bilinear downscale to 256 × 256 → JPEG-encode (q75, 96 KB cap). Native pickers + camera-roll access deferred to Beta if UX feedback demands. Resolved OQ#11 2026-05-11. |
| Authentication & storage | None for v1. stretch Cloud account with authentication and storage; vendor TBD when stretch is promoted. | v1 is fully local; no auth, no cloud. The stretch-goal cloud backend gives multi-tenant authoring with per-owner isolation. See Backend Schema and Authentication. |
| 2D animation/effects | Unity native — UGUI, Animator, particle system, Shader Graph | First-class tooling for snappy animations, custom shaders, and "big reveal" moments. |
| Code-driven tweens | DOTween | Sequences, easings, and chained tweens for UGUI elements and the Designer's preview. Mature and widely used in Unity; pairs cleanly with Animator/Timeline rather than competing with them. |
| 2D mini-games | Unity native | Same engine as the rest of the app; no embed required. |
| 3D content | Unity native | First-class from day one. Whether any v1 object type actually uses 3D is a content decision, not a platform constraint. |
Rationale for each load-bearing choice is in Decisions.
Designer Shell
The Designer is a single .NET MAUI Blazor Hybrid app that runs on Windows and macOS (Catalyst) from one C# codebase, desktop only. Razor components form the entire authoring UI — shell chrome, panels, properties inspector, dialogs. Slide preview is a dedicated Quiz.Preview Unity project built only for WebGL and embedded inside a BlazorWebView as a <canvas>. The same WebGL build serves both desktop and the future Blazor WASM web tool (Stretch).
iPad / Android tablet authoring is Stretch and ships from the web-based Designer codebase.
For the rationale and the alternatives that were rejected, see Decisions. For the live stack table see Tech Stack. For the full inventory of authoring-shell entry-points (menus, dialogs, panels, shortcuts, context menus) and where each leads, see Designer surfaces.
Diagram
Tier 1 — MAUI Blazor Hybrid shell (single .NET process)
One .NET 8/9 process per launched Designer. Targets Windows and macOS (Catalyst — UIKit-on-Mac) from one source tree.
Subsystems:
- MAUI shell — application bootstrap, platform integration, theme, lifecycle. Hosts one or more
BlazorWebViewinstances. - Window manager — MAUI
WindowAPI. Each tool window / palette / dialog can be a separate OS-level window on Windows + macOS, each hosting its ownBlazorWebView. - Razor component library — every authoring surface (main shell, slide list, palette, properties inspector, library panel, push-to-Host dialog, settings, etc.) is a Razor component. Components are project-internal for v1; promotion to a separate library or NuGet is deferred until a second consumer (the Blazor WASM web tool) actually needs the share.
- Quiz Model + Undo — C# in-memory authoring state. Source of truth. Razor components bind to it; mutations go through commands so they're undoable.
- File I/O —
.quizsave / load, asset import. Native file pickers via MAUI platform abstractions. - JS bridge — thin C# layer that pushes slide state to the embedded
Quiz.PreviewviaunityInstance.SendMessageand receives ready / rendered / error events back via JS interop.
Tier 2 — Quiz.Preview Unity project (WebGL build, embedded)
A sibling Unity project to Quiz.Host / Quiz.Client / Quiz.Remote, built only for WebGL, embedded in BlazorWebView as a <canvas> via the Unity loader.
Responsibilities:
- Render a single
.quizslide given a JSON state push from the Razor shell. - Expose a
SendMessage-based API for: load slide, update property, set selection. - Emit JS events back to the shell:
ready,slide-rendered,error. - No editing UI — purely a render target driven by the Razor shell.
Quiz.Preview is the renamed Quiz.Designer Unity project, repurposed: the previous project's authoring chrome is gone now that authoring lives in MAUI Blazor. Asmdef and namespace renames follow the project rename.
Quiz.Preview may diverge from Quiz.Host / Quiz.Client in narrow ways the WebGL environment requires (e.g. tap-only input, preview-mode-specific render hints), but scenes, prefabs, materials, shaders, and the overall render setup stay shared via the com.quiz.shared-assets UPM package. Visual fidelity stays aligned with Host / Client without forking the asset tree.
Tier 3 — Foundation
Shared libraries / packages referenced by every consumer:
- Quiz.Core (.NET Standard 2.1) — schema, validation, scoring,
.quizread/write. Referenced by the MAUI Blazor app + every Unity project (Quiz.Host,Quiz.Client,Quiz.Remote,Quiz.Preview). com.quiz.shared-assets— UPM package; scenes, prefabs, materials, shaders shared across the four Unity projects. Carries the slide / element rendering setup that needs to look identical between Host / Client / Preview. See Repository Layout.com.quiz.runtime— Unity-aware adapter package; main-thread marshalling, MonoBehaviour scaffolding, object-type plugin contract. Used by Unity projects only.
Pixel-fidelity trade-off
Designer preview is ~95-98% pixel-equivalent to native Host / Client. The same Unity scenes render in WebGL via WebGL2/WebGL1 shader profiles instead of D3D11 / Metal / Vulkan, with subtle drift in shadow filtering, post-processing, and AA.
- Gain: one preview integration across Win, Mac, and the future web tool. No HWND / NSView reparenting. No shared-texture compositing. No
--preview-flagged Host build. - Cost: preview is not bit-identical to play. Authors validating subtle visuals run a true Host build for final QA.
No pixel-diff harness or CI gate is planned — drift is accepted up-front.
Multi-window
MAUI.Window API; each tool window or palette can be a separate OS-level window, each hosting its own BlazorWebView. Real OS chrome, real OS-level focus, real OS-level minimise / maximise / close.
Editing flow
- User opens a
.quiz(or starts a new one). Razor shell loads it throughQuiz.Coredeserialisation. - User selects a slide. Razor design surface renders an approximate / form-driven view of the slide for fast editing.
- Razor pushes the slide's state (JSON) to
Quiz.Previewover the JS bridge.Quiz.Previewrenders the slide pixel-accurate (modulo WebGL drift). - User edits a property. Razor updates Quiz Model (commands → undoable), pushes the delta to
Quiz.Preview. WebGL re-renders. - User clicks "Send to Host" — same
.quiz-over-WebSocket flow as the live-play path.
Default asset library
The Designer ships with a built-in default library of small, pre-licensed assets the author can drop into a new quiz. The library covers two asset families today:
- Buzzer jingles — a small curated set of generic clips ("ding", "buzz", "horn", "fanfare") authors can pick from when configuring
core.buzzer-inputelement behaviour and team-customisation rules (F-DE-29). - Premade avatars — a starter avatar set authors can hand to teams as a photo-free alternative (F-DE-30).
The library lives inside the Designer install (under the app's resources, not in the quiz). When the author picks a clip / avatar from the library and adds it to the active quiz, the Designer copies the file into the in-memory .quiz package's resources/audio/buzzers/ or resources/avatars/ folder. The selection is registered in the manifest's buzzers[] / avatars[] array. The package therefore stays self-contained — a Host that doesn't have the Designer installed still has every byte it needs to play the chosen jingle or render the chosen avatar.
Library contents and licensing (resolved 2026-05-11):
| Family | Count in v1 | Source / licence |
|---|---|---|
| Buzzer jingles | 10 | CC0 / public domain (Freesound CC0 archives). Zero attribution needed; distributable inside any author-shipped .quiz. |
| Premade avatars | 24 | CC0 / public domain (OpenGameArt CC0 archives or commissioned-then-released-CC0). Same distribution terms. |
Library updates ride app releases — no content endpoint, no auth, no extra infrastructure in v1. New clips ship when the Designer ships. The author is expected to bring their own assets for any "this is my quiz personality" moment beyond the starter set.
Run from slide — local Host process spawn
Per F-DE-27, the Designer can launch a local Quiz.Host process on the same machine starting at the currently-selected slide. This is the toolbar ▸ Run CTA (and F5). The Designer:
- Saves the in-memory quiz to a scratch
.quiz(or uses the on-disk path if clean). - Spawns the platform Host binary as a child process, passing
--quiz <path> --start-at-slide <index> --launched-from-designer. - The Host opens in operator-window mode (F-HO-25) if multiple displays are connected — audience window on the primary external display, operator window on the laptop / iPad. On a single-display machine the operator surfaces are an overlay.
This is local-machine-only. Network push (Bonjour discovery → pick a remote Host) stays under File → Push to Host (F-DE-14).
Razor component library — MudBlazor
The Razor component library for the Designer is MudBlazor (MIT-licensed, Material-Design-based, actively maintained). Themeable via CSS variables to match the project brand. Multiple production-ready MAUI Blazor Hybrid + MudBlazor starter templates exist in the community.
Multi-window configuration
Multi-window is supported via Application.Current.OpenWindow(...) on Windows and Mac Catalyst.
- Windows — works zero-config (each
Windowhosts its ownBlazorWebView). - Mac Catalyst — add a
SceneDelegate.cs(subclassingMauiUISceneDelegate) underPlatforms/MacCatalyst/, plus aUIApplicationSceneManifest(UIApplicationSupportsMultipleScenes = true) block inPlatforms/MacCatalyst/Info.plist. - Windows + WebView2 — set the
WEBVIEW2_USER_DATA_FOLDERenv var to a writable path underFileSystem.AppDataDirectorybefore any WebView control initialises (required when the app installs underProgram Files).
Build-plan tasks track each.
Repository Layout
How the apps and their shared code are arranged on disk in this monorepo, and how shared code/assets are wired into each app. The high-level decision — one shared C# class library consumed by every app via UPM (Unity) and project reference (MAUI) — is recorded in Decisions; this page is the concrete on-disk realisation.
Top-level structure
Quiz/
├── Quiz.Designer/ # .NET MAUI Blazor Hybrid — authoring app (Win/Mac/iPad)
├── Quiz.Host/ # Unity project — TV-show app
├── Quiz.Client/ # Unity project — team device app
├── Quiz.Remote/ # Unity project — quizmaster controller
├── Quiz.Preview/ # Unity project — WebGL slide-render target embedded in Designer
├── packages/ # Shared local UPM packages (source-on-disk)
│ ├── com.quiz.core/ # Pure C# (.NET Standard 2.1) — schemas, DTOs, scoring
│ ├── com.quiz.runtime/ # Unity-aware shared runtime — registry, networking glue
│ └── com.quiz.shared-assets/ # Cross-app prefabs, fonts, brand assets, shaders
├── docs/ # Generated PDF/HTML artefacts (derived; do not edit)
└── .claude/ # Knowledgebase, skills, context
Quiz.Designer/ is a .NET MAUI Blazor Hybrid project — a regular .NET solution / .csproj. Razor components form the entire authoring UI; the project bundles a Unity WebGL build of Quiz.Preview/ and embeds it via BlazorWebView. See Designer Shell.
Quiz.Preview/ is a Unity project, sibling to Quiz.Host / Quiz.Client / Quiz.Remote, that builds only for WebGL and is consumed by the MAUI Blazor Designer as its embedded slide-render target. It is the renamed previous Quiz.Designer Unity project; authoring UI moved to the MAUI Blazor Designer, leaving this project as a pure render target. Asmdefs, namespaces, and folder names follow the rename.
Each Unity project sits at the repo root as a sibling, not nested. Unity needs an entire project tree per app (Assets/, ProjectSettings/, Packages/, Library/, etc.) and these trees do not nest cleanly. Sibling folders is the only layout Unity tolerates without per-platform symlink gymnastics.
Because four Unity projects can be open in Unity Editor at the same time, AI tooling that drives the editor must route each call to the correct editor instance. See Unity MCP for the routing protocol — without it, edits land in the wrong app's Assets/ tree.
The on-disk shape inside each project's Assets/_Game/ is documented in Per-App Scaffolding.
Shared code via local UPM packages
Shared code lives under packages/ as one or more local UPM packages. Each Unity project's Packages/manifest.json references them with a relative file: path:
{
"dependencies": {
"com.quiz.core": "file:../../packages/com.quiz.core",
"com.quiz.runtime": "file:../../packages/com.quiz.runtime",
"com.quiz.shared-assets": "file:../../packages/com.quiz.shared-assets"
}
}
file: references are not a registry round-trip — Unity reads the source files directly out of packages/. Edits made from any of the four projects propagate live with hot reload. The package.json inside each package is the minimal sentinel Unity needs to recognise a folder as a package; it is not a packaging or publishing step.
Why local UPM packages, not symlinks or DLL drops
| Option | Why not |
|---|---|
Symlinks / NTFS junctions from each Assets/ into a shared folder |
Junctions don't replicate cleanly across git clone; symlinks need core.symlinks=true on Windows; both can produce duplicate-import warnings or GUID churn if two projects ever resolve the same asset via different absolute paths. |
DLL drop into Assets/Plugins/ |
Loses jump-to-definition, no live edits across apps, requires a build step in the shared library on every change. Acceptable as a fallback but a worse developer experience. |
| Git submodule for shared code | Adds a git submodule update step to every clone and every shared-code change, with no benefit over a local folder in the same repo. |
Local UPM packages give source-level edits, stable asset GUIDs, and zero extra steps for contributors — git clone produces a working tree.
Package responsibilities
com.quiz.core
- Target:
.NET Standard 2.1, noUnityEnginereferences. - Contents: quiz schemas, DTOs, scoring rules, message envelopes, object-type plugin contract interfaces, the WebSocket protocol state machine and socket lifecycle, the mDNS coordination logic, the session state machine, the snapshot/replay format. Sockets and discovery are business logic, not engine logic — see Separation of Concerns.
- Why pure C#: so headless test harnesses, future server-side tooling, and CI-only runs can reference it without a Unity install. Also referenced by the MAUI Blazor Hybrid Designer as a regular NuGet-style project reference — the same .NET Standard 2.1 target serves both Unity and modern .NET hosts. Also:
UnityEngineAPIs are main-thread-only, and socket / mDNS work happens on background threads anyway, so making the constraint architectural is cheaper than enforcing it per-class. - Asmdef:
Quiz.Core.asmdef,noEngineReferences: true.
com.quiz.runtime
- Target: Unity-aware (depends on
UnityEngine). - Contents: Unity adapters around the engine-free networking and protocol code in
com.quiz.core— main-thread marshalling,MonoBehaviourlifecycle binding, View / ViewController scaffolding, the object-type registry implementation that wiresIObjectTypeHost/Client surfaces to UGUI prefabs. - Depends on:
com.quiz.core. - Why separate from core: keeps the pure-C# library testable headless; everything that touches Unity APIs lives here. This layer is intentionally a thin adapter — see Separation of Concerns — How the Unity-aware layer adapts engine-free code.
- Asmdef:
Quiz.Runtime.asmdefreferencingQuiz.Core.
The package split also drives where each kind of test lives — pure-C# edit-mode tests in com.quiz.core/Tests/, Unity-aware play-mode tests in com.quiz.runtime/Tests/, per-app play-mode tests in each Quiz.{App}/Assets/_Game/Tests/. See Testing for the full test layout and TDD convention.
com.quiz.shared-assets
- Contents: brand fonts, palette ScriptableObjects, mascot rig, shared object-type UGUI prefabs, shaders, audio stings, and the slide / element rendering setup shared between
Quiz.Host,Quiz.Client, andQuiz.Previewso the Designer's WebGL preview matches what Host / Client render at quiz time. - Depends on:
com.quiz.runtime(so prefabs can reference sharedMonoBehaviourtypes). - Why a separate package: assets evolve on a different cadence from logic, and the GUID stability guarantee is most valuable here — every cross-app prefab reference resolves through a single on-disk location. Used by all four Unity projects (
Quiz.Host,Quiz.Client,Quiz.Remote,Quiz.Preview).
The split is conservative — if any package ends up trivially small at MVP, it can be folded into the next one up. Going the other way (splitting later) is harder because callers fan out.
Assembly definitions and dependency direction
Game-side assemblies in each app → Quiz.Runtime → Quiz.Core
↓
(Unity engine APIs)
App-specific code references Quiz.Runtime and (transitively) Quiz.Core. The shared packages never reference app-specific code. This mirrors the module discipline documented in the unity-development skill's modules page: packages are modules, only one level higher up the tree.
Adding a new Unity project later
If a fifth app is ever introduced, the steps are:
- Create the Unity project as a sibling at the repo root (
Quiz.{Name}/). - Add the three
file:package entries to itsPackages/manifest.json. - Reference the shared assemblies from its game-side asmdef.
No changes to packages/ are needed — the shared packages stay symmetrical across all apps.
Adding a new shared package later
If a new shared concern emerges that doesn't fit the existing three (e.g. a com.quiz.test-utilities for cross-project test fixtures):
- Create
packages/com.quiz.{name}/with apackage.jsonandRuntime/(and optionallyEditor/andTests/) folders, each with its own asmdef. - Add a
file:entry to every Unity project'sPackages/manifest.jsonthat needs it. - Document the new package's responsibility in this page's "Package responsibilities" section.
Separation of Concerns: Business Logic vs Engine Logic
A guiding architectural principle, applied across all four apps: business logic is pure C# and lives in com.quiz.core; engine concerns live in com.quiz.runtime and per-app code. The boundary is enforced by noEngineReferences: true on the Quiz.Core asmdef — com.quiz.core cannot accidentally take a UnityEngine dependency.
This page is the why and the rule of thumb. The on-disk realisation is in Repository Layout; the load-bearing decision behind a single shared C# library is in Decisions.
What counts as business logic (lives in com.quiz.core)
- Quiz schemas, DTOs, manifest format, slide / element / canvas data models.
- Scoring rules and computation — including late-submission rules, tiebreakers, per-round and per-slide configurations.
- Session state machine — current slide pointer, team list, scores, per-element state, the rules for legal transitions.
- Live-play protocol — message envelope schemas, the state machine that interprets them, the dispatch table that routes typed messages to handlers.
- WebSocket lifecycle — accepting connections, reading/writing frames, reconnect logic, backoff. The actual socket I/O happens on background threads with no
UnityEnginecalls. - Service-discovery coordination — the mDNS protocol logic, advertised-record state, "host discovered" event generation.
- Quiz package format I/O — reading and writing
.quizarchives, manifest validation against the registered object-type catalogue. - Object-type plugin contract interfaces (
IObjectType, the schema accessor, the protocol-extension hooks). - Crash recovery snapshot / replay — serialisation and deserialisation of session state, validation that a snapshot matches the loaded
.quiz.
What counts as engine concerns (lives in com.quiz.runtime or per-app code)
- Rendering — UGUI canvases, shaders, particles, post-processing.
- Audio playback —
AudioSource, mixers, ducking. - Animation — Animator, Timeline, DOTween tweens.
- Input handling — touch, keyboard, gamepad, stylus.
- Prefab instantiation, scene loading, addressables.
MonoBehaviourlifecycle binding —Awake,OnEnable,Update, etc.- Main-thread marshalling — bridging events that arrived on background threads (sockets, mDNS) to the Unity main thread so view code can react.
- View / ViewController scaffolding wiring
IObjectTypeHost/Client surfaces to UGUI prefabs.
Why this boundary
- Headless testability. The protocol state machine, scoring rules, and snapshot round-trip can run in a plain
dotnet testharness with no Unity install. Tests are fast and reliable; CI doesn't need a Unity license to run them. - Concurrency correctness.
UnityEngineAPIs are main-thread-only. The WebSocket server and mDNS listener necessarily run on background threads. If networking lived in the Unity-aware layer it would have to either block the main thread or build its own thread-marshalling discipline anyway — putting it incom.quiz.coremakes the constraint architectural rather than per-class. - Single source of truth across apps. The Designer, Host, Client, and Remote all need to interpret the same schemas and message envelopes the same way. A shared pure-C# library guarantees that — the same types, the same validators, the same state machine.
- Forces explicit interfaces. Crossing the boundary requires defining an event, callback, or interface. That friction is good — it surfaces hidden coupling instead of letting "view code reaches into protocol code" creep in.
- Forward compatibility. If part of the system ever moves server-side (the Stretch cloud-backed authoring path, an internet-relay server) or a future client uses a different engine, the business-logic library is portable as-is. v1 doesn't depend on this — it's a free option.
How the Unity-aware layer adapts engine-free code
com.quiz.runtime is intentionally a thin adapter, not a replication of business logic. The shape is consistently:
- Instantiate the engine-free objects from
com.quiz.core(e.g.WebSocketServer,SessionStateMachine,MessageDispatcher). - Subscribe to their events on whatever thread they fire on.
- Marshal those events to the Unity main thread (via
SynchronizationContextor a queue drained inUpdate). - Re-emit them as Unity-friendly C# events /
UnityEvents for views and game code to consume. - On the way back out (player input, view actions), call into the engine-free objects directly from the main thread — they're thread-safe at their public API, or document where they aren't.
If a method in com.quiz.runtime is doing more than adapt / marshal / wire-up — if it's deciding something about scoring, protocol state, or session validity — that decision belongs back in com.quiz.core.
Rule of thumb when in doubt
Could this code, conceptually, run on a server with no display? If yes, it's business logic — put it in
com.quiz.core. If it would be meaningless without a screen, an audio device, or an input device, it's an engine concern — put it incom.quiz.runtimeor per-app code.
The socket layer answers "yes" to that question — sockets don't need a display. So sockets live in core.
Quiz Package Format
Quizzes are stored and transferred as .quiz files (zip archives) with the following structure:
mypub_quiz_v3.quiz
├── manifest.json # Quiz metadata, schema version, ordered slide list,
│ # round groupings, declared object-type registry,
│ # buzzers[] + avatars[] registries
├── slides/
│ ├── slide_001.json # Slide def: id, title, round_id, timing, scoring,
│ │ # host_canvas, client_canvas
│ ├── slide_002.json
│ └── ...
└── resources/
├── images/ # general slide imagery referenced by elements
├── audio/
│ ├── slides/ # general slide audio referenced by elements
│ └── buzzers/ # author-bundled buzzer jingles (F-DE-28, F-CL-15)
├── video/
└── avatars/ # author-bundled premade avatars (F-DE-30, F-CL-14)
A .quiz package contains data only — no runtime code. Each element on a slide references an object type by id (e.g. core.text) and version. The Designer can only emit packages whose declared type ids/versions match its own built-in registry; the Host and Client refuse to load a package whose declared types they do not have built-in. Bundle-supplied object types (a object_types/ folder shipping C# behaviours + prefabs alongside the data) are deferred to a stretch goal — see Open Questions.
A slide is the unit of presentation. Each slide carries a Host canvas (a fixed 1920×1080 virtual canvas, scaled to fit the connected display) and a Client canvas (a responsive layout that adapts across phone and tablet form factors). Both canvases hold elements; the same slide can have completely different content on each. (For term definitions see the Glossary.)
An element is a placed instance of an object type with its own per-instance properties. An object type is a pluggable module — see Object-Type Architecture.
A round is a contiguous range of slides sharing a title and scoring metadata — equivalent to a PowerPoint section. Rounds are optional metadata over the slide list, not the unit of presentation.
The manifest.json is validated against a C# schema in the shared class library, so the Designer cannot produce — and the Host cannot accept — a malformed package. The manifest also declares every object type the quiz uses (with required schema versions); apps refuse the package if any declared type is missing or version-incompatible against the app's built-in registry.
A quiz package declares its overall schema version in the manifest. The Host gracefully refuses or upgrades older packages. Maximum package size is 200 MB, capped to keep the always-eager-push live-play model viable on older mobile devices (2–3 GB RAM). This was cut from an earlier ~500 MB ceiling when OQ#8 resolved on always-eager-push without a streaming fallback (resolved 2026-05-11). Authors needing more than 200 MB of media in a single quiz are out of scope for v1.
The manifest also declares a theme (an enum: dark (default), light, plus brand presets) that the Host, Client, and Remote render the quiz against. The Designer's chrome theme is independent of this — an author may author in light theme but ship a dark-themed quiz. Per-quiz custom palette objects (richer than the enum) are tracked in Stretch. Theming overall is Beta scope; MVP and Alpha render against the default dark theme regardless of manifest declaration.
Team-customisation resources
Two resources/ sub-buckets exist solely to feed the team join flow at runtime; neither is referenced by slide elements:
resources/audio/buzzers/— short author-supplied audio clips, one per file. Each clip is registered inmanifest.jsonunder the top-levelbuzzers[]array as{ id, name, file, duration_ms }. At join, the Client lists these bynamefor the captain to preview + pick from (F-CL-15). At runtime, the Host plays the picked clip when that team wins a buzzer race (F-HO-27). Clips are copied into the bundle when the author picks from the Designer's default library (F-DE-29) so the package is self-contained — a Host that doesn't have the Designer installed still has everything it needs to play the chosen jingle.resources/avatars/— author-supplied premade avatar images. Each is registered inmanifest.jsonunderavatars[]as{ id, name, file }. The Client offers these as a fallback when the team captain declines the camera permission or doesn't want to use a photo (F-CL-14).
Both arrays are optional. If absent, the Client hides the buzzer picker (Host falls back to a silent buzz tone) and the avatar picker (Client only offers photo capture).
Photos captured by teams at join are not stored inside the .quiz — that is a session-scoped artefact, transmitted over the WebSocket join message and held in the Host's session snapshot only. See Networking.
Designer→Host transfer
The Designer→Host transfer of a .quiz file over local Wi-Fi is described in Networking.
Networking
Local Wi-Fi (primary path, v1)
The Host runs an in-process WebSocket server on its local network interface. It advertises itself on the local network via Bonjour/mDNS. The same server handles three message families:
- Designer transfer — receiving
.quizpackage transfers from a Designer. - Client live-play — running live quiz sessions with team Clients.
- Remote control — accepting control connections from the Remote app. MVP carries the minimum viable controls (discovery, pairing, mirror, host-notes, live state, advance/go-back); the rich control command set lands in Alpha.
Each family has distinct typed envelopes defined in the shared C# class library, but they share one transport, one TLS posture, and one server lifecycle.
Designer→Host package transfer
When the Designer is in "send to Host" mode, it discovers Hosts on the local network via the Bonjour advertisement, the author picks one, and the Designer pushes the .quiz file (already saved to local disk via the export action) to the chosen Host over the WebSocket connection.
Gating: manual confirm per push. The Host shows a prompt — "Designer X wants to send 'My Quiz' (50 MB). Accept?" — every time a transfer arrives. One tap to accept. Same-LAN auto-accept was rejected because pub Wi-Fi is shared and a stranger on the network pushing nonsense to the Host would be operationally bad. A future PIN-pairing flow that lets a known Designer auto-accept thereafter is possible but not in v1.
The Host receives the package, validates the manifest, resolves every declared object type against its built-in registry (see Object-Type Architecture), refuses on any mismatch, and otherwise stores it locally for play.
Wire-level details (resolved 2026-05-11):
| Aspect | Pinned default |
|---|---|
| Chunk size | 64 KB per WebSocket frame |
| Progress reporting | Sender pushes a transfer-progress event after each chunk is acknowledged by the receiver |
| Integrity | CRC32 per chunk; receiver verifies, requests re-send of any failed chunk |
| Resume after disconnect | Receiver tracks offset on disk; sender resumes from the last acknowledged offset on reconnect |
| Wire compression | None (.quiz is already a zip archive — extra compression wastes CPU on both ends) |
| Max package size | 200 MB (see Quiz Package Format) |
The full framing schema lives in the (planned) Transfer Protocol document; the defaults above pin the design space so the prototype validates specifics rather than the architecture.
The on-disk format being transferred is described in Quiz Package Format.
Live play and per-Client distribution
Clients discover the Host via the same Bonjour service, connect, and exchange typed messages defined in the shared C# class library. When a Client joins a session, the Host eagerly pushes the slides' Client-canvas content plus the resources those elements reference (images, audio clips that play on the Client, etc.) over the WebSocket. Buzzer-jingle audio (see Quiz Package Format) is not pushed to Clients — those clips play on the Host, not the Client, and the Client only needs the list of { id, name, file } records to render the join-time picker. The Client downloads each individual jingle file lazily when the team taps "preview" on the picker.
Team join — identity payload
The team join message is a single WebSocket frame that carries everything the Host needs to register a new team:
| Field | Type | Required | Notes |
|---|---|---|---|
team_name |
string | ✓ | Per F-CL-2. |
team_colour |
string (token from .quiz manifest) |
Beta only | Per F-CL-12. |
avatar_choice |
{ kind: "photo", jpeg: <base64>, w, h } \| { kind: "premade", avatar_id: <manifest avatars[].id> } |
✓ from Alpha onwards | Per F-CL-14. Photo is JPEG, square, target ≤ 64 KB / 256 × 256 px — Client downscales + encodes before sending. Premade refers to a manifest.avatars[].id (Designer-bundled, see package format). |
buzzer_jingle_id |
string | null | Alpha (if quiz has buzzers) | Per F-CL-15. References manifest.buzzers[].id. null if the quiz bundles no jingles or the team didn't pick one. |
client_token |
string | ✓ from Alpha onwards | Per-Client persistent identity for crash-recovery rejoin (F-CL-10). |
The Host validates the payload, stores the photo / jingle-choice in its session snapshot (so crash recovery preserves them), and broadcasts a "team joined" event to the rest of the session participants.
The photo is held in memory by the Host and written to the session-snapshot file for crash recovery; it is cleared at session end unless cloud-backed team identity (F-HO-23 Stretch) is enabled.
Joining mid-quiz: progress UI + jump-to-current. A Client joining after the quiz has begun shows a progress bar while the eager push transfers; on completion, the Client jumps directly to the slide the quiz is currently on. Earlier rounds are not replayed (acceptable for the pub-quiz format — a late team picks up where the room is). Slides advanced during the eager push are queued and applied in order when the push completes.
Timer authority and clock sync. The Host is authoritative on time. Clients render a local countdown that periodically reconciles with the Host's "time-remaining" tick. On time-up, the Host emits a "lock" message; Clients stop accepting input. Submissions arriving after the lock are rejected — unless the slide is configured (by the author) to accept late submissions, in which case the Host applies the configured scoring rule (no penalty / fixed penalty / per-second decay). The quizmaster (via the Remote, or directly on the Host) can override the timer for a slide live: extend, skip, manually lock, or manually unlock. Override semantics are documented in the (planned) Live Play Protocol document.
Reliability requirements:
- A single client misbehaving must not affect other clients or the Host.
- The Host must survive client disconnects and reconnects gracefully (a reconnecting Client may re-receive the eager push or a delta).
- The Host must tolerate brief Wi-Fi instability and brief backgrounding.
- A team device dying or backgrounding the Client app does not crash the session.
The detailed message envelopes, lifecycle, and reconnection semantics belong in the (planned) Live Play Protocol document.
Crash recovery (Alpha onwards)
The Host snapshots session state to disk after every scoring event and every slide advance. State is sufficient to resume: current slide pointer, team list (team_id, team_name, score), per-element state where the element flagged itself "stateful" (e.g. a timer's remaining time at the moment of snapshot, a leaderboard's last reveal index), and the live-play protocol's last sequence number per Client.
If the Host process crashes, the operator relaunches the Host. On launch with a saved session that matches the loaded .quiz, the Host prompts the operator to resume or start fresh. Resume restores the snapshot; the Host re-advertises on Bonjour; Clients reconnect using their persisted team identity (a stable token written to local storage on first join) and re-attach as their original team with their score intact. The reconnecting protocol path is the same one used for Wi-Fi-blip recovery; the only difference is the Host's state was rebuilt from disk rather than from memory.
This is a v1 capability, not a Stretch goal — it lands in the Alpha phase. MVP runs the disconnect/reconnect protocol but does not persist state across a Host restart.
Remote control
The Remote pairs with one Host at a time. Discovery is the same Bonjour advertisement Designers and Clients use. Pairing is gated — manual confirm on the Host the first time a given Remote connects, or QR-code pairing where the Host shows a code that the Remote scans (final UX is a build-plan decision).
Once paired, the Remote opens a control-message-family WebSocket to the Host. Messages flow both ways. The MVP and Alpha cuts of this protocol are:
| Direction | MVP — minimum viable controls | Alpha — rich control commands |
|---|---|---|
| Host → Remote | Periodic mirror of the Host canvas (scaled-down). Per-slide host-notes from the loaded .quiz. Live state — current scores, current timer remaining, current slide index. |
(No additions — the same telemetry stream supports the richer commands.) |
| Remote → Host | Advance, go-back. | Jump to a specific slide. Trigger element reveals (e.g. show the leaderboard, show the answer). Lock / unlock Client input. Extend / skip the timer. Override scoring per team. |
The MVP control-message envelope schema must reserve room for the rich commands so adding them in Alpha is purely additive — no protocol break.
The Remote does not expose any participant-facing surface. It is the quizmaster's tool, not a team's tool.
A session supports zero or one Remote in v1. Multi-Remote (co-quizmasters on separate phones) is not in scope.
Internet-based play (future)
Architecturally accommodated, not a v1 feature — see Stretch. The plan is an optional relay (Cloudflare Durable Objects or similar) that proxies WebSocket traffic between Host and Clients in different locations. Same protocol; different transport.
Session-code join
Discovery shifts from Bonjour to a host-issued session code (F-X-6 Stretch) — a short alphanumeric token (e.g. Q7-3K9-FX) the Host displays prominently on its idle / join screen alongside the existing LAN QR. Clients type the code on the discover screen instead of (or in addition to) picking a Host from the Bonjour list. Remotes do the same on their pairing screen.
The code resolves through the relay to the Host's WebSocket endpoint; from that point on the join + live-play message families are bit-identical to the LAN path. Only the transport — direct WebSocket vs relay-tunnelled WebSocket — differs. This is deliberate: a v1 Client / Host / Remote that's been written against the LAN message envelopes runs over the relay with no protocol change.
Concrete relay design — code allocation, lease + expiry, NAT traversal fallback, billing / abuse controls — is the work tracked in Open Questions #3. UI surfaces for the code-entry flow are tracked in Client surfaces, Host surfaces, and Remote surfaces.
Slide and Object-Type Architecture
The atomic unit a quiz is built from is the slide. A slide owns two canvases (Host and Client) and an ordered list of elements placed on each. Elements are instances of object types.
An object type is a self-contained pluggable module that contributes:
- Schema — a C# type (in the shared class library or a plugin assembly) describing the element's data shape, with a declared schema version and a JSON serialisation contract.
- Designer editor surface — a Razor component (with backing C# view-model) that lets the author edit an element's properties in the Designer's properties inspector. The pixel-accurate render comes from the Host runtime surface (#3) running in the embedded
Quiz.PreviewUnity WebGL build via the JS bridge (see Designer Shell). - Host runtime surface — a UGUI prefab + C# behaviour that renders and animates the element on the Host canvas during live play.
- Client runtime surface — a UGUI prefab + C# behaviour that renders the element on the Client canvas, including any input handling.
- Optional protocol extension — typed message envelopes the object type sends or receives between Host and Client (e.g. an answer submission, a buzzer press). Extensions register on the shared message envelope; the core protocol does not need to change to add a new object type.
Each object type has a globally unique, namespaced type id (e.g. core.text, core.image, core.multiple-choice-input, music.spotify-clip-player) and a schema version. Quiz manifests declare the type ids and versions they use; apps resolve them against a built-in registry compiled into each app and refuse a package they cannot fully resolve. The manifest declaration is part of the Quiz Package Format.
In v1, packages contain no runtime code — every object type a quiz uses must already be present as a built-in in the app's registry. Adding a new object type means adding a new self-contained module to the apps' source tree (it registers itself in the registry on startup) and shipping a new app build; the core orchestration, slide-rendering, and editor code do not change. Bundle-supplied object types — runtime modules carried inside a .quiz — are deferred to a stretch goal alongside cloud-backed authoring (see Open Questions).
Built-in object-type catalogue
The platform ships these built-in object types. Phase columns show when each lands — see Phases. Thirteen types ship across the v1 phases (MVP, Alpha, Beta); a further four are tracked in Stretch as pub-quiz formats that earn their own type rather than being expressed as configuration on existing types.
| Type id | Phase | Purpose | Typical placement |
|---|---|---|---|
core.text |
MVP | Rendered text block. | Either canvas. |
core.multiple-choice-input |
MVP | Tappable answer options; first/only selection submitted. | Client canvas. |
core.free-text-input |
MVP | Text-entry answer field. | Client canvas. |
core.numeric-input |
MVP | Closest-wins scoring instead of exact-match; numeric validation. Essential for estimation rounds and tiebreakers. | Client canvas. |
core.timer |
MVP | Countdown / elapsed timer. Default behaviour: lock Client input on time-up. Author-configurable per slide: lock-or-not, late-submission scoring rule (none / fixed penalty / per-second decay), and whether the quizmaster can manually extend or skip. The Host is authoritative on time — see Networking. | Either canvas. |
core.leaderboard |
MVP | Per-team standings. Trigger-driven reveal: the author places the leaderboard wherever in the slide order they want it to appear, configures its reveal trigger (on slide entry / on quizmaster trigger / after delay), and optionally configures a reveal animation (full table / one row at a time / bottom-up). The leaderboard is not Host app chrome that appears automatically — it's an explicitly placed element under author and quizmaster control. | Either canvas. |
core.image |
Alpha | Static image from the package's resources/images/. |
Either canvas. |
core.audio-clip |
Alpha | Playable audio clip from resources/audio/. |
Host canvas. |
core.video |
Alpha | Video clip from resources/video/. |
Host canvas. |
core.drawing-input |
Alpha | Finger drawing capture (touch-only in v1); can mirror to Host. | Client canvas (with optional Host mirror). |
core.buzzer-input |
Alpha | Buzzer with first-press semantics across clients. When a team wins the press, the Host plays that team's chosen buzzer jingle (F-HO-27) — a clip the team picked at join from the quiz's bundled set (F-CL-15, F-DE-28). The element itself owns only the press-race protocol + on-canvas visual; jingle resolution happens in the Host's team-identity registry, not in the element. | Client canvas. |
core.categories-input |
Beta | Multi-line free-text. "Name 5 X." Each line scored independently. | Client canvas. |
core.mini-game |
Beta | Embeds a mini-game. The mini-game framework itself is a separate build-plan deliverable. | Client canvas (typically). |
core.match-pairs-input |
Stretch | Drag/connect UX. Multi-pair scoring (each correct pairing scores). | Client canvas. |
core.ranking-input |
Stretch | Drag-to-reorder UX. Scoring all-or-nothing or partial-credit per item-in-correct-position. | Client canvas. |
core.hotspot-input |
Stretch | Tap on image coordinates. "Where on the map is X?" / anatomy / geography. | Client canvas. |
core.true-false-input |
Stretch | Visually distinct from multiple-choice (✓ / ✗ vs A / B). Lower priority — multiple-choice covers it functionally. | Client canvas. |
Leaderboard and timer are object types (not Host/Client app chrome) so the author has full creative control over when and where they appear. Reveal triggers are a general element capability, not specific to the leaderboard: any element can declare a reveal trigger (on slide entry / on quizmaster trigger / after delay) and a reveal animation. The slide schema reserves a reveal field on every element for this. The detailed plugin contract (interfaces, lifecycle, serialisation rules, version negotiation, the reveal field shape) belongs in the (planned) Object Types document.
Shared element properties
Every placed element — regardless of object type — carries the same shared property block in addition to its object-type-specific fields. These are the fields a generic editor (move / lock / reveal) needs to operate on any element, and the Designer's right-pane Properties tab renders this block once at the top followed by the object-type-specific editor below.
| Property | Type | Description |
|---|---|---|
id |
string (UUID) | Stable per-element identifier; survives reorder and clipboard ops. Used by reveal-trigger commands and protocol messages targeting a specific element. |
name |
string (optional) | Author-facing label. Defaults to the object type's display name; surfaces in the slide outline, error messages, and reveal-target picker. |
type |
string | Object-type id (e.g. core.text, core.multiple-choice-input). Bound at insertion; not re-editable. |
canvas |
host | client |
Which canvas the element lives on. An element can be duplicated to the other canvas (independent instance), but a single element is on exactly one. |
transform.host |
{ x, y, w, h, rotation, z-order } |
Host-canvas placement — absolute coordinates against the fixed 1920×1080 virtual canvas. Present only when canvas == host. |
transform.client |
{ anchors, region \| stack-slot, z-order } |
Client-canvas placement — responsive layout. Anchors and region pin the element to one of the slide's declared regions / stacks. Present only when canvas == client. |
visibility |
always | triggered |
always = visible from slide-entry. triggered = hidden until its reveal.trigger fires. |
reveal.trigger |
on-entry | on-cue | after-delay |
When this element appears (or animates in) on the canvas during play. on-cue is fired by the quizmaster from the Host operator window or paired Remote (F-HO-25, F-RE-9). |
reveal.delay-ms |
int | Used only when reveal.trigger == after-delay. Milliseconds from slide-entry. |
reveal.animation |
string | Animation key — fade, slide-in-left, pop, etc. Animations are part of the design language; the catalogue lands in Beta. MVP supports none and fade. |
lock |
bool | Designer-only flag. When true, the canvas selection chrome prevents drag / resize / rotate; inspector edits remain available. Lands in Beta. |
notes |
string (optional) | Internal author notes about the element (not the slide-level host-notes per F-DE-19). Lands in Beta. |
The shared block is serialised once per element; per-object-type schemas extend it. The slide schema is canonical and pinned in the shared C# class library — see Open Questions #2. The right-pane inspector renders the shared block in a fixed order (Identity → Transform → Reveal → Lock) followed by the object-type editor.
For the per-app UI inventory of where these properties surface in the Designer (inspector sections, context-menu items, lock badge, reveal cue affordance) see Designer surfaces — §8 Right pane.
Authentication
v1: No authentication on any app. The Designer, Host, and Client all run unauthenticated. Designer→Host transfer is gated by being on the same local network (and the Host accepting the incoming connection through its UI), not by credentials. See Networking for the transfer model.
Stretch goal (alongside cloud-backed authoring):
- Author / Host: cloud account sign-in with email magic link as the universal path, plus Sign in with Apple (required when distributed via the iOS App Store), Google, and optionally Microsoft. The same account is used on both apps. Auth provider (managed Postgres + auth-as-a-service / cloud-native identity / similar) decided when this stretch is promoted. The data the Author authenticates against is laid out in Backend Schema.
- Client: Still no authentication — each team enters a team name on joining a session (one shared Client device per team). Team names are ephemeral and not stored beyond the session.
Backend Schema — stretch goal, not v1
Cloud-backed authoring (account, library, Designer→cloud upload, Host→cloud download) is in Stretch. v1 has no cloud backend; the Designer saves .quiz files to disk and transfers them to the Host over the local network — see Networking. The schema below sketches the entities so the v1 architecture stays compatible with the eventual cloud path. The vendor (managed Postgres + S3-compatible object storage) is decided when the stretch is promoted; the SQL shape below is illustrative, not vendor-specific.
profiles
id uuid PK references the auth provider's user id
display_name text
created_at timestamptz
quizzes
id uuid PK
owner_id uuid FK → auth user id
title text
description text
tags text[]
current_version_id uuid FK → quiz_versions
created_at timestamptz
updated_at timestamptz
deleted_at timestamptz -- soft delete
quiz_versions
id uuid PK
quiz_id uuid FK → quizzes
version_number int
storage_path text -- path in object storage
size_bytes bigint
notes text
pinned boolean default false -- protect from version pruning
created_at timestamptz
Storage layout: quizzes/{owner_id}/{quiz_id}/v{n}.quiz
Tenant isolation: users can SELECT/INSERT/UPDATE/DELETE only rows where owner_id matches their authenticated identity. Object-storage paths are similarly scoped to the user's own prefix. Whether this is enforced via row-level security at the database or via the application layer depends on the chosen vendor, decided when this stretch is promoted.
Version retention: the latest 20 versions per quiz are kept, plus any versions marked pinned = true. Older versions are pruned by a scheduled job. Soft-deleted quizzes are recoverable from a "trash" view for 30 days.
Auth flows on top of this schema are covered in Authentication.
CI / CD
The project uses Azure DevOps Pipelines for continuous integration and deployment. Source lives in Azure Repos; pipelines are defined in YAML in this repo and run on a mix of Microsoft-hosted agents and a project-owned self-hosted Mac mini agent.
Why Azure DevOps Pipelines
- Single platform with project management. Issues, boards, repos, pipelines, wikis, and artefacts all under one tenant (Azure DevOps for project management covers the boards / sprint side).
- Self-hosted macOS agents are first-class. Apple-only build steps (Mac Catalyst, iPad / iOS, macOS code-signing, notarisation) require a Mac, and the project's Mac mini covers them without paying for a cloud-Mac runner.
- Unity build pipeline support. Unity Editor activations, license caching, and Player builds for Win / macOS / iPad / Android run cleanly under ADO Pipelines tasks, including the Quiz.Preview WebGL build.
Agent pool layout
| Agent | Hosted by | Purpose |
|---|---|---|
Azure Pipelines (Microsoft-hosted) |
Microsoft | Windows builds (Quiz.Designer MAUI Win, Quiz.Host / Client / Remote Unity native Windows player), shared C# analysers + tests, .NET Standard 2.1 library tests, generic CI tasks. |
MacMini (self-hosted) |
Project | macOS Catalyst + iPad / iOS builds (Quiz.Designer MAUI for those targets), Quiz.Host / Client / Remote Unity macOS + iOS Player builds, Quiz.Preview WebGL build, code-signing, notarisation. |
The Mac mini runs the latest macOS supported by Apple's developer toolchain, has the current Xcode + iOS / Catalyst SDKs installed, and holds the project's signing certificates and provisioning profiles in the macOS Keychain.
Pipelines
Each YAML pipeline lives at the repo root under .azure-pipelines/. Pipelines are intentionally small and composable; multi-stage pipelines orchestrate them.
| Pipeline | Trigger | Agent | What it runs |
|---|---|---|---|
pr-validation.yml |
Pull request | Both pools as needed | Build everything that compiles on each agent + run all fast tests + lint + format. Required check before merge. |
quiz-core.yml |
Push to main touching packages/com.quiz.core/ or its tests |
Microsoft-hosted | dotnet test headless; .NET Standard 2.1 library; no Unity. |
quiz-runtime.yml |
Push to main touching packages/com.quiz.runtime/ |
Mac mini (Unity license) | Unity edit-mode + play-mode tests under com.quiz.runtime/Tests/. |
quiz-host.yml |
Push to main touching Quiz.Host/ |
Mac mini for macOS / iOS Player; Microsoft-hosted for Windows Player | Unity Player builds for the configured Host platforms; play-mode tests. |
quiz-client.yml |
Push to main touching Quiz.Client/ |
Mac mini for macOS / iOS Player; Microsoft-hosted for Windows / Android | Same shape as Host. |
quiz-remote.yml |
Push to main touching Quiz.Remote/ |
Mac mini / Microsoft-hosted as appropriate | Same shape. |
quiz-preview-webgl.yml |
Push to main touching Quiz.Preview/ or packages/com.quiz.shared-assets/ |
Mac mini (Unity license) | Unity WebGL Player build of Quiz.Preview/; output published as a pipeline artefact consumed by quiz-designer.yml. |
quiz-designer.yml |
Push to main touching Quiz.Designer/, or successful quiz-preview-webgl.yml artefact |
Microsoft-hosted (Win); Mac mini (macOS Catalyst + iPad) | .NET MAUI build of Quiz.Designer/ for each target framework, bundles the published Quiz.Preview WebGL artefact under wwwroot/, runs Razor / xUnit unit tests. |
release.yml |
Manual run / git tag | Both pools | Signs, packages, and publishes installers to the configured store / distribution channels. |
Pipelines that need a Unity license use a shared "Unity" service connection holding the activation file; the activation is cached on the Mac mini agent so cold starts are fast.
Caching + artefacts
- NuGet package cache keyed by
**/packages.lock.json. - Unity Library/ cached per-project per-agent so re-imports skip on incremental builds.
- WebGL build output published as a pipeline artefact and consumed by
quiz-designer.ymlso the Designer build stays decoupled from the Unity build. - Test results published as JUnit XML so the ADO test report aggregates across pipelines.
Branching + PR policy
- Trunk-based;
mainis always green. - Feature work happens on short-lived branches; PRs target
main. pr-validation.ymlis a required status check onmain— branches don't merge without it.mainis build-verified by the per-area pipelines plus a nightlyrelease.ymldry run that does not publish.
Local mirror
.azure-pipelines/ is checked in. Devs can lint pipeline YAML locally with az pipelines runs list and validate with az pipelines validate. The pipeline definitions are owned end-to-end by the repo, not the ADO portal, so changes are reviewed in PR alongside the code they validate.
Testing
How testing is practised on this project. This page is the development-process source of truth — the Build Plan deliberately does not restate test tasks per item; instead the build plan's Definition of Done references this page.
Test-driven development is the default
For feature work, write the failing test first, make it pass with the smallest viable change, then refactor. The "feature work" boundary is anything that delivers user-visible behaviour, exercises a network protocol, or touches scoring/persistence/rendering pipelines.
The following work is excepted from TDD:
- Initial template scaffolding, asmdef/namespace renames, project-settings tweaks, asset relocations, splash skeleton — captured in Per-App Scaffolding. Tests on bootstrap glue are flaky and brittle to scene layout changes; the smoke-test in Editor is sufficient signal at scaffolding time.
- One-off documentation, knowledgebase, or CI configuration changes.
- Dependency upgrades that don't change behaviour.
Anything else: failing test first.
Where each kind of test lives
| Layer | Location | Mode | Why |
|---|---|---|---|
| Pure C# domain — schemas, scoring, protocol state, package format | packages/com.quiz.core/Tests/Runtime/ |
Edit-mode | com.quiz.core is .NET Standard 2.1 with noEngineReferences: true. Tests run headless under NUnit, no Unity Player needed. Fast in CI. |
| Unity-aware shared adapters — registry impl, slide adapters, networking glue | packages/com.quiz.runtime/Tests/Runtime/ |
Play-mode | Touches MonoBehaviour lifecycle, main-thread marshalling. Requires the Unity Player. |
| App-specific behaviour — Designer authoring flows, Host session, Client team-play, Remote control | Quiz.{App}/Assets/_Game/Tests/ (each app) |
Play-mode | Exercises scenes, prefabs, UI interactions. Per-app because behaviour diverges. |
| App boot-chain smoke (Setup → MainMenu/Designer) | Quiz.{App}/Assets/_Game/Tests/Smoke/ |
Play-mode | Per-app — verifies the boot chain reaches its target scene with zero console errors. Replaces the manual MCP smoke-tests once they exist. |
Each test folder owns its own asmdef referencing only the assemblies under test plus nunit.framework. The com.wildfiregames.core test fixtures (if any) are not used directly — test the project's code, not the third-party.
Conventions
- Test class name mirrors the production class with
Testssuffix:ScoringEngine→ScoringEngineTests. - Fixture-per-class for unit tests; one
[Test]method per behaviour. - Async tests use
[UnityTest]+IEnumeratorfor play-mode,[Test] async Taskis fine for edit-mode under NUnit ≥ 3.13. - Arrange / Act / Assert sections separated by blank lines, no comments to label them — names already do that.
- Avoid
MonoBehaviour.SendMessage, reflection on private members, or scene-dependent fixtures unless the test is explicitly an integration test. - Test data lives next to the test in
TestData/— never reach into productionResources/from tests.
CI gate
Azure DevOps Pipelines runs the test suites on every PR and every push to main:
- Edit-mode suite for
com.quiz.coreruns headless underdotnet test(no Unity, fast). - Play-mode suite for
com.quiz.runtimeand each Unity project runs under the Unity test runner on the appropriate agent (Mac mini for macOS / iOS, Microsoft-hosted for Windows). - Razor / xUnit tests for
Quiz.Designerrun on Microsoft-hosted Windows agents and on the Mac mini for Catalyst / iPad targets. - A red test fails the pipeline;
pr-validation.ymlis a required check before merge.
Local fast feedback: edit-mode tests run from Unity's Test Runner window in <1 s per fixture; CLI parity via Unity.exe -batchmode -runTests -testPlatform editmode for Unity, dotnet test for Quiz.Core and Quiz.Designer.
Smoke-test workflow during scaffolding
Before automated play-mode smoke tests are wired up, the scaffolding workflow uses Unity MCP as a lightweight stand-in:
set_active_instanceto the target editor.read_console action=clear.manage_editor action=play.- Wait for the boot chain.
read_console types=["error","warning"].manage_editor action=stop.
This is a manual cross-check, not a regression gate. Once the per-app Tests/Smoke/ suite lands, that suite becomes the gate and the manual MCP procedure is retired.
Cross-references
- Repository Layout — package responsibilities — describes the
com.quiz.core/com.quiz.runtime/com.quiz.shared-assetspackage split that this page's table mirrors. - Per-App Scaffolding — work explicitly excepted from TDD per the section above.
- Build Plan — Definition of Done references this page rather than restating tests per item.
AI Tooling
The project uses AI tooling throughout the development workflow to accelerate implementation, surface issues early, and keep code quality high.
Tools
| Tool | Role |
|---|---|
| Claude Code | Primary AI coding assistant. Drives feature implementation, refactoring, code review, and knowledgebase maintenance. |
| OpenCode | Secondary AI coding assistant. Complements Claude Code on tasks where a different model behaviour is preferred or when running in parallel on independent work. |
| Self-hosted Qwen3.5 LLM | Local fallback for both Claude Code and OpenCode when usage limits are hit on the primary providers. Keeps the team unblocked without paying for additional cloud-LLM throughput. |
No other AI providers are part of the project's tooling.
How AI tooling is used
- Feature implementation. Engineers describe a build-plan item or user story to the AI assistant; the assistant reads the surrounding code, drafts the change, and applies it. The engineer reviews the diff, runs the tests, and merges through the standard PR flow.
- Knowledgebase maintenance. Spec changes, decision capture, and design notes are written through the AI assistant against the knowledgebase so the docs stay aligned with the code.
- Code review. AI-driven review passes are run on every PR alongside human review — the assistant flags issues a human reviewer might miss (consistency, naming, dead code, untested branches).
- Local automation. AI assistants drive the project's editor MCPs (Unity, etc.) so editor-level operations (creating prefabs, wiring scenes, running tests) can be scripted from a chat interface rather than clicked through manually.
- Failover to self-hosted Qwen3.5. When Claude Code or OpenCode hit their rate limits, the assistant transparently falls back to the project's Qwen3.5 instance running on internal hardware. Capability is reduced relative to the cloud models, but the team remains unblocked for the duration of the limit.
Conventions
- AI-generated changes are always reviewed by a human before merge — same standard as any other code change. Definition of Done is the same regardless of who (or what) wrote the code.
- AI-driven docs changes go through the same git + PR review as code changes; the auto-generated PRD / Build Plan / Design Spec outputs are derived artefacts and not edited directly (see the
docsskill). - Test discipline (Testing) applies to AI-written code identically: failing test first, end-to-end test for user-facing features, lint + format pass.
The detailed multi-editor routing protocol the AI tooling uses to drive the four open Unity Editors (Host, Client, Remote, Quiz.Preview) lives in the team-facing unity-mcp.md page; that level of detail is operational, not requirements-level, and lives in the knowledgebase rather than the PRD.
Per-App Scaffolding
The on-disk shape each Unity project converges on. Covers Host, Client, Remote, and the renamed Quiz.Preview render-only project. The MAUI Blazor Designer is a separate .NET project — see Designer Shell.
com.wildfiregames.core (third-party UPM) provides the underlying Registry, GameBehaviour, SceneReference, audio system, event queue, pooling, settings, and entity infrastructure every Unity project depends on. com.quiz.runtime (the local UPM package described in Repository Layout) holds the Quiz-domain Unity adapters: object-type registry implementation, slide/element MonoBehaviour scaffolding, networking glue. Game-loop bootstrap (PersistentSystems, SaveSystem, AudioController, scene loaders) lives per-app because each app's needs diverge quickly.
Universal conventions
Apply to all four Unity projects (Host, Client, Remote, Quiz.Preview).
_Game/is the per-app root underAssets/. Three sibling folders:Entities/,Modules/,Scenes/. Plus_Global/for cross-scene assets (audio, fonts, sprites, configuration, input actions).- One asmdef per app, named
Quiz.{App}.Game.asmdef, root namespaceQuiz.{App}.Game. companyName:QuizCompany.productName:Quiz.{App}.- Active Input Handling: Input System Package (or "Both" if a fallback to legacy is needed). Input asset at
_Global/Configuration/InputActions.inputactions. - Persistent prefab:
_Game/Scenes/_Common/Resources/PersistentSystems.prefab— auto-loaded at startup, holds the persistent systems for that app (audio, save, etc.). Singleton; one instance ever. - Empty scene:
Scenes/Empty/Empty.unity— used as a transient scene during cross-scene swaps. - Setup scene:
Scenes/Setup/Setup.unity— the cold-boot bootstrapper. Host/Client/Remote use it as an invisible bootstrap that loads persistent systems then transitions to the main menu. Quiz.Preview replaces it with the WebGL boot scene.
Quiz.Preview designer
A Unity project, sibling to Host / Client / Remote, built only for WebGL — see Designer Shell.
Scenes:
- Preview/Preview.unity — single render scene with the slide canvas + camera; entry point for the WebGL boot.
Per-scene scripts:
- Preview/Scripts/PreviewBoot.cs — listens for JS messages (LoadSlide, UpdateProperty, SetSelection); calls back via [DllImport("__Internal")] for ready / rendered / error events.
References: com.quiz.shared-assets, com.quiz.runtime, com.quiz.core via Packages/manifest.json.
Project Settings:
- WebGL build target only.
- defaultScreenWidth/Height flexible (canvas size driven by the host page).
- WebGLMemorySize tuned for the largest expected scene.
Host host
The live-quiz runtime — renders the quiz to a TV / projector and runs the in-process WebSocket server. Two top-level scenes:
Scenes/MainMenu/MainMenu.unity— pre-quiz lobby: load a.quiz, accept Designer transfers, list connected teams, start session.Scenes/Play/Play.unity— the slide-driven runner that advances through the quiz, dispatches element behaviours, and runs the live session.
Per-scene scripts and prefabs:
- MainMenu/Scripts/MainMenuViewController.cs — drives the lobby UI.
- Play/Scripts/PlayController.cs — orchestrates slide advance, element runtime, scoring, and session state.
- _Common/Resources/PersistentSystems.prefab and _Common/Scripts/PersistentSystems/* — audio bus + persistent loader.
- _Common/Scripts/SaveSystem/* — session-state persistence per Networking — Crash recovery.
References: com.quiz.shared-assets, com.quiz.runtime, com.quiz.core.
Project Settings:
- defaultScreenOrientation: 3 (LandscapeLeft); portrait autorotates disabled.
- defaultScreenWidth: 1920, defaultScreenHeight: 1080 (TV / projector target).
Client client
The team-device app — joins a session, renders each slide's Client canvas, collects answers. Two top-level scenes:
Scenes/MainMenu/MainMenu.unity— discovery + join: list visible Hosts, enter team name, join.Scenes/Play/Play.unity— render the current slide's Client canvas, dispatch input elements, show score.
Per-scene scripts and prefabs:
- MainMenu/Scripts/MainMenuViewController.cs — discovery + join UI.
- Play/Scripts/PlayController.cs — slide rendering, input dispatch, score display.
- _Common/Resources/PersistentSystems.prefab + scripts — audio + persistent loader.
- _Common/Scripts/SaveSystem/* — team-identity persistence per F-CL-10.
References: com.quiz.shared-assets, com.quiz.runtime, com.quiz.core.
Project Settings:
- defaultScreenOrientation: 4 (AutoRotation) — Client supports portrait and landscape on phones and tablets.
Remote remote
The quizmaster's pocket controller — pairs with a Host, mirrors its display, sends control commands. Two top-level scenes:
Scenes/MainMenu/MainMenu.unity— discovery + pairing: list visible Hosts, pair (manual confirm or QR), connect.Scenes/Play/Play.unity— render the mirrored Host canvas + host-notes + live state, send control commands.
Per-scene scripts and prefabs:
- MainMenu/Scripts/MainMenuViewController.cs — discovery + pairing UI.
- Play/Scripts/PlayController.cs — mirror rendering, host-notes display, live-state display, command dispatch.
- _Common/Resources/PersistentSystems.prefab + scripts — audio + persistent loader.
References: com.quiz.shared-assets, com.quiz.runtime, com.quiz.core.
Project Settings:
- defaultScreenOrientation: 1 (Portrait); landscape autorotate disabled, portrait autorotate enabled.
Key Architectural Decisions and Tradeoffs
Load-bearing decisions, with the rationale that justifies each. Decisions are recorded so they are not relitigated without a good reason.
Unity for live-play apps (Host, Client, Remote); .NET MAUI Blazor Hybrid for the Designer shell, with an embedded Unity WebGL preview via BlazorWebView. Unity gives a single engine for live-play visuals (animation, particle, shader, 2D and 3D) and unifies the build/CI shape across the three live-play apps. The Designer is forms-heavy authoring (slide list, palette, properties inspector, modal panels, menus) plus a slide preview — concerns where Razor components reach Windows + macOS (Catalyst) in a single C# codebase, and where the same component library is reusable for the future Blazor WASM web tool (Stretch). Mac uses Catalyst (UIKit-on-Mac). See Tech Stack and Designer Shell.
Slide preview is a dedicated Quiz.Preview Unity project, WebGL-only, embedded in BlazorWebView. Quiz.Preview is a sibling Unity project to Quiz.Host / Quiz.Client / Quiz.Remote that builds only for WebGL and serves as the slide-render target for the Designer (and the future web Designer). It shares scenes, prefabs, materials, and rendering setup with Host / Client via the com.quiz.shared-assets UPM package — visual output stays aligned across the four Unity projects without forking the asset tree. Razor components push slide state to Quiz.Preview over the in-process JS bridge (unityInstance.SendMessage); the WebGL build emits ready / rendered / error events back via [DllImport("__Internal")] callbacks. Two Unity build targets across the broader product: native (Host / Client / Remote) and WebGL (Preview).
Pixel-accuracy is ~95-98%. WebGL renders differ from native D3D11 / Metal / Vulkan in shadows, AA, and post-processing. Accepted trade-off for one preview integration that serves desktop and the future web tool with no per-platform native plumbing.
The Designer ships desktop only in v1 — Windows + macOS (Catalyst) — single MAUI Blazor codebase. iPad and Android tablet authoring are deferred to Stretch and ship from the web-based Designer codebase. v1 Designer focus is the deepest authoring workflow on desktop (multi-window, real OS chrome, file pickers, drag-drop) without compromising for the touch surface.
One shared C# class library, consumed by every Unity project via UPM and by the MAUI Blazor Designer via project reference. The library is .NET Standard 2.1 and UnityEngine-free. Schemas, scoring logic, message contracts, the WebSocket protocol state machine, and mDNS coordination logic are all shared; UI, rendering, audio, input, and scene code is per-app. This avoids RPC-style network code between apps that don't need to talk to each other directly — the Designer and Host meet in the cloud, not over the wire — and lets headless test harnesses reference the library too. The on-disk realisation — pure-C# com.quiz.core, Unity-aware com.quiz.runtime, and com.quiz.shared-assets for cross-app prefabs — is in Repository Layout. The pure-C# / Unity-aware boundary is in Separation of Concerns.
Server embedded in the Host (not a separate process). Simpler deployment, fewer moving parts. The Host always operates in a foregrounded, user-attended state during a quiz, which is the state in which all target platforms (iPad, Windows, macOS, Android tablet) can reliably run an in-process server. The same server handles three message families: Designer transfer, Client live-play, and Remote control. Unity scripts run on the main thread, so the WebSocket server runs on a background thread and dispatches state changes back via Unity's main-thread synchronisation primitives. See Networking.
Slide-based authoring with two independent canvases per slide. A quiz is an ordered list of slides, each with a Host canvas (TV-format, fixed 1920×1080 virtual) and a Client canvas (phone/tablet, responsive). The two canvases share the slide's identity, timing, and scoring but can hold completely different elements — the question text might fill the Host canvas while the Client canvas shows tappable answer options. The split lets the same quiz be a "TV show" experience on the projected display and a snug, one-handed phone experience for participants, without the author maintaining two parallel quizzes.
Object-type plugin model is first-class from v1. The schema is modular: new element types can be added without changing core code. This shapes the Quiz Package Format (manifest declares object types by id/version), the shared class library (defines the plugin contract; ships no concrete object-type implementations), and the apps (each maintains a built-in registry that types register themselves into on startup). For v1, every object type a package uses must already be present as a built-in on every app loading the package; bundle-supplied object types are deferred to a stretch goal. See Object-Type Architecture.
v1 is fully local — cloud-backed authoring is a stretch goal. v1 has no auth, no account, no cloud library. The Designer exports .quiz files to disk and sends them to the Host over the local network via a UI action; the Host loads packages from disk. The architecture stays compatible with a future cloud path so adding it later is additive, not a rewrite. Cloud vendor (managed Postgres + S3-compatible object storage) is decided when the stretch goal is promoted. See Backend Schema.
Multi-tenant in the eventual cloud schema, even though authoring starts single-user. Designing for multi-tenancy when the cloud path lands is cheaper than retrofitting it.
v1 .quiz packages contain no runtime C# code. Packages are data only (manifest, slides, resources). Every object type a quiz uses must already be a built-in on the Host and Client, version-matched. Avoids the security/sandboxing/signing surface of loading bundled C# at runtime. Bundle-supplied object types are deferred to the cloud-backed-authoring stretch goal.
Host eagerly distributes per-Client slide content at join time. When a Client connects to a session, the Host pushes the full set of Client-canvas content and Client-side resources for the quiz over the WebSocket. Per-slide play then issues only "show slide N" commands — no per-slide network round-trips, no stutter waiting for media to download mid-display. The trade-off is a larger payload at join (typically tens of MB for a media-heavy quiz, well within local Wi-Fi bandwidth), which is the right side of the trade for a single-room quiz format.
Local network is the primary live-play transport, not a fallback. Once a quiz is on the Host, no internet is required. Hard requirement because pub Wi-Fi cannot be relied on. Internet-based live play is a future, optional transport — not a substitute for the local-network primary path.
Host persists session state to disk; crash recovery is a v1 requirement, not Stretch. The Host writes a session snapshot (current slide, team list, scores, slide-element state) after every scoring event and every slide advance. If the Host process crashes, the operator relaunches the Host with the same .quiz and resumes the session — Clients reconnect using their persisted team identity and re-attach as the same team. The cost is small (single JSON file, frequent small writes) and the outcome is materially better operational behaviour for what is a live, time-pressured event. Alpha-phase deliverable.
The Remote app is a fourth distinct app, not a mode of the Client. A quizmaster walking the room with a controller is a different ergonomic problem from a team submitting answers. Folding both into one app conflates roles, complicates pairing/auth, and makes the per-slide UI compromise (control panel vs answer surface). Separate apps keep each role's UI clean and the message families distinct on the wire.
3D content is a first-class option from day one. Unity makes 3D scenes and effects native rather than bolt-on. Whether any v1 object type uses 3D is a content decision driven by the built-in object-type catalogue, not a platform constraint.
Requirements
Functional Requirements
Numbered functional requirements per app, plus cross-cutting requirements. Per-app responsibilities and platforms are in Applications; per-app UI entry-point inventory in UI Surfaces; non-functional targets are in Non-Functional Requirements. Each requirement is tagged with its target Phase.
Phase tags: MVP, Alpha, Beta, Production, Stretch. The Stretch tag means out-of-scope for the initial launch (v1) and tied to features in Stretch.
Designer designer
- F-DE-1 Stretch The author can sign in to their cloud account.
- F-DE-2 MVP The author can create a new quiz with title, description, and tags.
- F-DE-3 MVP The author can add, edit, reorder, and delete slides within a quiz.
- F-DE-4 MVP The author can group contiguous slides into rounds (sections) with a round title and round-level scoring metadata, and can ungroup or reorder rounds.
- F-DE-5 MVP The author can place, position, configure, reorder, and delete elements on a slide's Host canvas (fixed 1920×1080 virtual canvas).
- F-DE-6 MVP The author can place, configure, reorder, and delete elements on a slide's Client canvas (responsive layout — anchors / regions / stacks).
- F-DE-7 MVP The author can browse the available object-type palette (the v1 built-in catalogue) and add an instance of any object type to either canvas. The palette grows as object types are introduced across phases.
- F-DE-8 Alpha The author can attach images, audio, and video to a quiz as resources, and reference them from element properties. Media object types (
core.image,core.audio-clip,core.video) land in Alpha; MVP supports the resource-attachment plumbing only insofar as the.quizpackage format is canonical. - F-DE-9 MVP The author can configure scoring rules per slide and per round (point values, time bonuses, late-submission penalties).
- F-DE-10 MVP The author can configure timing per slide (and defaults per round): slide duration, whether time-up locks Client input, whether late submissions are accepted, the scoring rule for late submissions (none / fixed penalty / per-second decay), and whether the quizmaster can override the timer live.
- F-DE-11 MVP The author can preview a slide's Host and Client canvases live in the embedded Unity preview surface as they edit.
- F-DE-12 MVP The author can save a quiz to the local file system as a
.quizfile. - F-DE-13 MVP The author can re-open an existing
.quizfile from the local file system. - F-DE-14 MVP The Designer can discover Hosts on the local Wi-Fi network (via Bonjour/mDNS) and send a saved
.quizfile to a chosen Host over the local network. - F-DE-15 Stretch The author can save a quiz to the cloud, creating a new version, and view/restore version history.
- F-DE-16 Stretch The author can soft-delete a quiz and recover it from a "trash" view within 30 days.
- F-DE-17 Stretch The author can use stylus input for sketching and annotation where useful (Apple Pencil on iPad; equivalent stylus on supported non-Apple tablets). v1 is touch-only on every platform — drawing inputs accept finger touch; pressure/tilt/palm-rejection is the stretch addition.
- F-DE-18 MVP The
.quizpackage format is canonical: the manifest declares every object type used and its required schema version. v1 packages contain no runtime code. - F-DE-19 MVP The author can attach host-notes to each slide — free-form text authored in a PowerPoint-style notes pane below the canvas in the Designer (always visible while editing a slide). Notes accept either plain text or markdown; markdown is rendered at runtime. Notes appear on the Remote and on the Host operator window (per F-HO-25) during play — never on the audience-facing Host main display and never on any Client. Used for hints, answer keys, presentation cues.
- F-DE-20 MVP The author can configure each placed element's reveal trigger (on slide entry / on quizmaster trigger / after delay) and reveal animation. The reveal field is part of every element on every slide.
- F-DE-21 Beta The author can define the per-team theming options for the quiz: a palette of team colours and a set of avatars Clients can pick from at join. See Beta — Per-team theming.
- F-DE-22 Stretch The author can request AI-generated question suggestions on a topic (multiple-choice options, distractors, host-notes drafts). Generated content is staged for the author's review and explicit confirmation before it lands in a slide. See Stretch — AI-aided quiz authoring.
- F-DE-23 Stretch The author can clone a quiz template from the cloud library as the starting point for a new quiz. Depends on cloud-backed authoring.
- F-DE-24 Stretch The author can build a personal question bank — questions decoupled from any specific quiz — and pull questions from the bank into one or more quizzes. Depends on cloud-backed authoring.
- F-DE-25 Beta The Designer chrome ships dark and light themes. The author picks one as their working theme; the choice is persisted in app settings and is independent of the per-quiz theme an authored quiz declares.
- F-DE-26 Beta The author can declare a theme for the quiz in the manifest (
dark,light, or brand preset). The Host, Client, and Remote render the quiz against this theme during play. - F-DE-27 MVP The author can run the current quiz starting at the currently-selected slide from the Designer (toolbar "▸ Run" CTA or
F5). Activation is fire-and-spawn with no confirmation dialog — the Designer immediately spawns a local Host process on the same machine, passing the in-memory.quiz(or its on-disk path) and a start-at-slide pointer. The spawned Host registers an Esc-key handler that quits the runner and returns focus to the Designer; the Designer process remains alive throughout. Used for quick author-side validation without going through the network push-to-Host flow (F-DE-14). Push-to-Host stays as the network-discovery path; run-current-slide is local-machine-only. - F-DE-28 Alpha The author can attach buzzer jingles to a quiz — short audio clips bundled inside the
.quizunderresources/audio/buzzers/and listed in the manifest'sbuzzers[]array. These are the clips a team can pick from at join (F-CL-15) and the Host plays when that team submits a buzzer answer (F-HO-27). The bundling avoids music-licensing exposure on the app — the author owns the clips they ship. Required asset shape: short (≤ 4 s recommended, ≤ 8 s hard cap), mono or stereo, MP3 / OGG / WAV. Depends on the.quizpackage format gaining theresources/audio/buzzers/bucket + manifest field. - F-DE-29 Alpha The Designer ships a built-in default jingle library — a small curated set of generic buzzer clips covering the common "ding" / "buzz" / "horn" / "fanfare" archetypes. The author browses the library in the Designer and adds chosen clips to the active quiz; the selected clips are copied into the
.quizpackage'sresources/audio/buzzers/folder (not referenced by id from the Designer install), so the package stays self-contained when transferred to a Host that doesn't have the Designer installed. Built-in clips are pre-licensed for distribution as part of the quiz. - F-DE-30 Alpha The author can attach a premade avatar set to the quiz — image files bundled under
resources/avatars/and listed in the manifest'savatars[]array. Teams pick from this set at join if they don't want to use a photo (F-CL-14). The Designer's default library also ships a small starter avatar set the author can drop in, with the same self-contained-bundle rule as buzzer jingles (F-DE-29).
Host host
- F-HO-1 Stretch The host operator can sign in to their cloud account.
- F-HO-2 Stretch The host can list and download quizzes from the operator's cloud library.
- F-HO-3 MVP The host runs an in-process WebSocket server and advertises itself on the local network via Bonjour/mDNS — handling Designer transfers (when idle), live Client connections (during a session), and Remote control connections.
- F-HO-4 MVP The host can receive a
.quizfile pushed over the local network from a Designer. Gating: the Host shows a manual-confirm prompt for every incoming transfer, identifying the originating Designer and the file size. The Host validates the manifest on receipt and stores the file locally only if the operator accepts. - F-HO-5 MVP The host can load a
.quizfile from the local file system (manual file picker, for files transferred outside the Designer push flow). - F-HO-6 MVP Loaded quiz packages remain available offline.
- F-HO-7 MVP The host resolves every object type referenced by a loaded package against its built-in registry and refuses the package if any are missing or version-incompatible.
- F-HO-8 MVP The host can start a quiz session from a loaded package.
- F-HO-9 MVP The host displays a join screen showing connected teams (one Client device per team) and their team names.
- F-HO-10 MVP When a Client connects, the host eagerly pushes the slides' Client-canvas content and Client-side resources for the entire quiz over the WebSocket.
- F-HO-11 MVP The host advances through the quiz's slides in order, rendering each slide's Host canvas at the connected display's resolution and dispatching its element behaviours; "show slide N" commands are issued to Clients without further resource transfer.
- F-HO-12 Alpha The host plays attached media (images, audio, video) at the appropriate moment via the relevant element behaviours. Depends on the media object types added in Alpha.
- F-HO-13 Alpha→Beta The host runs interactive moments — animated reveals, transitions, mini-game presentation overlays — driven by element behaviours on the Host canvas. First-pass animation in Alpha; brand-true motion language and mini-game overlays in Beta.
- F-HO-14 MVP The host receives answers from clients (via element protocol extensions), applies scoring rules (including late-submission rules per F-DE-10), and displays the leaderboard when the slide places a
core.leaderboardelement with a triggered reveal. - F-HO-15 MVP The host handles client disconnects and reconnects gracefully (a Client device backgrounding does not crash the session); a reconnecting client may re-receive the eager push or a delta.
- F-HO-16 MVP The host is authoritative on time. It emits a periodic "time-remaining" tick and an authoritative "lock" message at time-up; Clients reconcile against this.
- F-HO-17 MVP The host operator can override the timer live for the current slide: extend, skip, manually lock, manually unlock. Override via the Remote is part of the Alpha rich-command set — see F-HO-24 and F-RE-9.
- F-HO-18 Alpha The host persists session state to disk after every scoring event and every slide advance — sufficient to resume the session if the Host process crashes and is relaunched with the same
.quizloaded. - F-HO-19 Alpha On launch with a saved session that matches the loaded
.quiz, the host prompts the operator to resume or start fresh. Resume restores the slide pointer, team list, scores, and per-element state; the host re-advertises on Bonjour and accepts reconnecting Clients as their original teams. - F-HO-20 MVP The host accepts a paired Remote over the WebSocket on a control message family. Pairing offers both options simultaneously: the Host displays a short numeric pairing code and a QR encoding the same code. The Remote can type the code manually or scan. Resolved per Open Questions #9.
- F-HO-21 MVP The host streams a periodic mirror of its current display, the current slide's host-notes, and live state (scores, timer remaining, slide index) to the paired Remote, and accepts the core navigation commands (advance, go-back) from it. The richer control-command set is F-HO-24.
- F-HO-22 Stretch The host offers a broadcast / streaming-friendly display mode with chrome and styling tuned for screen-capture: subdued backgrounds, boosted overlay readability, no in-room-only prompts. See Stretch — Broadcast view.
- F-HO-23 Stretch The host recognises returning teams (from previous sessions for the same quizmaster account) and offers them their previous identity at join. Depends on cloud auth + recurring-teams identity store.
- F-HO-24 Alpha The host accepts the rich control command set from the paired Remote, on top of the MVP advance/go-back: jump to a specific slide, trigger element reveals, lock/unlock Client input, extend/skip the timer, override scoring per team. Mirror of F-RE-9.
- F-HO-25 MVP The host supports a dual-window operator view on machines with multiple displays. The author / operator picks which connected screen renders the audience-facing main display (clean slide canvas, F-HO-11) and the Host opens a second OS-level window on another screen as the operator window — a same-machine equivalent of the Remote: live mirror of the audience screen, current slide's host-notes (F-DE-19), session HUD (timer, scores, slide index), and the same nav + control affordances the Remote exposes (F-HO-21, F-HO-24 at Alpha). On a single-display machine the operator window can be summoned as an overlay or skipped; the audience canvas is the only required surface.
- F-HO-26 Alpha The host renders the team photo (or fallback premade avatar) supplied by each team at join (F-CL-14) on the leaderboard rows, the join screen team roster, and any per-team callout (correct-answer ping, big-reveal celebration). Photos are stored in memory + the session-snapshot file for crash recovery; cleared at session end. No long-term persistence in MVP/Alpha (cloud sync of team identity is Stretch — F-HO-23).
- F-HO-27 Alpha The host plays the per-team buzzer jingle (chosen at join per F-CL-15) on its audio output the moment that team's buzzer-press wins the first-press race. If a team did not pick a jingle the Host plays a generic silent-buzz / haptic cue instead. Volume is governed by the Host's audio settings; the operator can mute jingles globally without ending the session.
Client client
- F-CL-1 MVP The client discovers active hosts on the local Wi-Fi network via Bonjour/mDNS.
- F-CL-2 MVP The client allows a team to enter a team name and join a host. v1 is one shared Client device per team; there is no per-individual identity within a team.
- F-CL-3 MVP On joining, the client receives the eagerly-pushed slide content and resources for the whole quiz from the host and caches them in memory. A late-joining Client shows a progress UI during the eager push and, on completion, jumps directly to the slide the quiz is currently on.
- F-CL-4 MVP The client resolves every object type referenced by a slide against its built-in registry and reports a fatal mismatch to the host if any are missing or version-incompatible.
- F-CL-5 MVP The client renders the current slide's Client canvas with its responsive layout adapting to the device's form factor, dispatching each element's runtime behaviour.
- F-CL-6 MVP→Alpha The client supports input modalities exposed by the relevant object types. MVP: multiple-choice tap, free-text entry. Alpha: drawing, gesture (buzzer).
- F-CL-7 Beta The client runs mini-games when a slide includes a mini-game element. Depends on the mini-game framework added in Beta.
- F-CL-8 MVP The client displays the team's score and standings.
- F-CL-9 MVP The client gracefully handles disconnection and reconnection.
- F-CL-10 Alpha The client persists its team identity (a stable token issued by the Host on first join) to local storage so it can rejoin a recovered session as the same team without re-entering the team name.
- F-CL-11 MVP The client renders the Host's authoritative timer state (countdown / locked / extended / skipped) and stops accepting input when the Host emits a "lock" message.
- F-CL-12 Beta At join, the team picks a colour and an avatar from the author-defined palette; the choice carries through every Client surface and every per-team Host moment (leaderboard rows, team-callouts).
- F-CL-13 Stretch A returning team can reclaim their previous identity (name, colour, avatar) when joining a session run by the same quizmaster. Depends on cloud auth + recurring-teams identity store.
- F-CL-14 Alpha At join, the team can take a photo with the device camera (Unity
WebCamTextureAPI — single cross-platform path, see Tech Stack and OQ#11) or pick from the quiz's bundled premade-avatar set per F-DE-30. The captured photo is centre-cropped to square, bilinear-downscaled to 256 × 256 px, JPEG-encoded at quality 75 on the Client; if the encoded size exceeds the 96 KB hard cap, the Client re-encodes at quality 60 before sending. The encoded JPEG is uploaded to the Host as part of the join message (Networking — Team join). The Host renders the photo on the leaderboard and any per-team callout (F-HO-26). The Client requests camera permission via the OS; if denied or unavailable the captain falls back to the premade avatar set without an error state. - F-CL-15 Alpha At join, the team picks a buzzer jingle from the set bundled into the loaded quiz (per F-DE-28). The choice is transmitted to the Host in the join message. The Client previews each jingle on tap before commit. If the quiz has no jingles bundled the buzzer-jingle picker is hidden and the Host falls back to a silent buzz tone. Depends on
core.buzzer-input(this phase, Alpha).
Remote remote
The Remote app ships in MVP with minimum viable controls — discovery, pairing, mirror, host-notes, live state, and the core navigation commands (advance / go-back). The richer control-command set lands in Alpha (F-RE-9).
- F-RE-1 MVP The Remote discovers active Hosts on the local Wi-Fi network via Bonjour/mDNS.
- F-RE-2 MVP The Remote pairs with one Host at a time. Pairing is gated (manual confirm on the Host the first time a given Remote connects, or QR-code pairing — final UX is build-plan).
- F-RE-3 MVP The Remote opens a control-message-family WebSocket to the paired Host.
- F-RE-4 MVP The Remote renders a live, scaled-down preview of what the Host's main display is currently showing.
- F-RE-5 MVP The Remote displays the per-slide host-notes (from F-DE-19) for the current slide.
- F-RE-6 MVP The Remote displays live session state: current scores, current timer remaining (the Host's authoritative state), and current slide index within the quiz.
- F-RE-7 MVP The Remote sends core navigation commands to the Host: advance, go-back. Richer control commands are F-RE-9.
- F-RE-8 MVP The Remote handles disconnection and reconnection gracefully (Wi-Fi blip recovery; rejoins the same paired Host without re-pairing).
- F-RE-9 Alpha The Remote sends the rich control command set to the Host on top of the MVP advance/go-back: jump to a specific slide, trigger element reveals (e.g. show the leaderboard, show the answer), lock or unlock Client input, extend or skip the timer, override scoring per team. Host side is F-HO-24.
Cross-cutting project
- F-X-1 MVP The shared C# class library defines all data schemas (quiz package, WebSocket messages across all message families) so every app stays in sync.
- F-X-2 MVP Schema versioning: a quiz package declares its schema version, and the Host gracefully refuses or upgrades older packages.
- F-X-3 Beta All apps use a shared design language (typography, color palette, motion vocabulary) — see Design Specification. MVP and Alpha are visually utilitarian; the brand-true treatment lands in Beta.
- F-X-4 Beta Host, Client, and Remote read the theme from the loaded quiz manifest (per F-DE-26) and render against that theme. Default is the dark brand theme.
- F-X-5 Beta All four apps ship a full audio language: branded stings (correct / incorrect / lock / time-up / big reveal / end of round / end of quiz), transition motifs, and a light optional ambient music bed. See Design Specification — sound design.
- F-X-6 Stretch Session-code joining for internet play. When the Internet-relay is reachable, the Host advertises a short alphanumeric session code (e.g.
Q7-3K9-FX). Clients on the discover screen can type that code instead of picking a Host from the Bonjour list, and Remotes do the same on their pairing screen. On the wire the join message is identical between LAN and internet paths; only the transport differs (direct WebSocket on LAN, relay-tunnelled WebSocket via Cloudflare Durable Objects or equivalent in internet mode — see Open Questions #3).
Non-Functional Requirements
| Area | Requirement |
|---|---|
| Reliability | Live quiz sessions must survive client disconnects, the Host backgrounding briefly, and minor Wi-Fi instability. No single client misbehaving should affect other clients or the Host. Crash recovery (Alpha onwards): if the Host process crashes, relaunching the Host with the same .quiz loaded must restore the session — current slide pointer, team list, and scores — and accept reconnecting Clients as their original teams. |
| Performance | UI animations target 60 fps on iPhone 12 / equivalent Android phone or tablet and above; ≥ 60 fps on supported desktops (Windows, macOS). Question transitions complete within 500 ms of trigger on the Host. Client message round-trip target under 200 ms on local Wi-Fi. Remote control message round-trip target under 200 ms on local Wi-Fi. |
| Compatibility | Designer: Windows 10/11, macOS 12+ (desktop only in v1). Host: Windows 10/11, macOS 12+, iPadOS 16+, Android 12+ tablets. Client and Remote: iOS 16+, Android 11+; also runs on iPad and Android tablets. iPad / Android tablet authoring is Stretch. All minimums are provisional — final confirmation against Unity's Editor and Player support matrix and real-device testing happens in the Beta phase. |
| Quiz scale | Support sessions of up to ~50 concurrent Client devices (one per team — see Glossary) on local Wi-Fi. With typical team sizes that covers ~150–300 participants per pub quiz; well beyond any realistic single-room format. |
| Quiz package size | Up to 500 MB per package (covers media-heavy rounds with audio and video). |
| Storage | v1: local-disk only on the Designer and Host (subject to device free space). stretch Cloud quotas: 5 GB per author; version retention: latest 20 versions plus any pinned versions. |
| Privacy | v1: no cloud account, so author data lives only on the Designer device and any Host they push to. Team names are ephemeral and not stored beyond the session. stretch Author data in the cloud is private to their account. |
| Offline operation | v1 is fully local — neither Designer nor Host nor Client requires internet at any point. Designer→Host transfer needs same-LAN connectivity, not internet. stretch Once a cloud quiz package is downloaded to the Host, the entire live experience runs without internet access. |
Phases
MVP
Goal: Prove the production architecture end-to-end on every supported platform, with the smallest feature set that still constitutes a working pub quiz. The output is a thing an author can use to design a quiz and run it for one or more teams in a room — no media, no minigames, no polish, but real.
This is the architecture. Nothing here is throwaway prototype code; the choices made in MVP carry through to Alpha, Beta, and Production.
In scope
Architecture and platform
- The Designer (.NET MAUI Blazor Hybrid
.csproj) and the four Unity projects (Host, Client, Remote, Quiz.Preview) initialised with the shared C# class library wired in via local UPM packages (Unity) and project / NuGet reference (Designer) — see Repository Layout. - DOTween installed across all four Unity projects.
- Azure DevOps Pipelines running per-area pipelines on Microsoft-hosted + self-hosted Mac mini agents — Unity builds (4 projects), .NET MAUI build (Designer), C# analysers,
dotnet format, tests on every PR + push tomain. - Pre-commit hooks for format and analyzer.
- Azure DevOps Boards configured with the project's sprint board (To Do / Blocked / In Development / Ready For Test / In Testing / Done columns) and PRD-decomposed Epics / Features / Stories / Issues populated.
- Cross-platform from day one — see OQ resolved: multi-platform sequencing. MVP must validate on:
- Designer: Windows + macOS (Catalyst) — single MAUI Blazor codebase, desktop only. Host: Windows, macOS, iPad, Android tablet. (iPad / Android tablet authoring is Stretch, not MVP.)
- Client: iPhone, Android phone, iPad, Android tablet.
- Remote: iPhone, Android phone, iPad, Android tablet.
Networking and transfer
- In-process WebSocket server in the Host (library to be picked during the load-bearing prototype — see Open Questions).
- Bonjour/mDNS service discovery — Host advertises, Designer and Client discover (see Open Questions).
- Designer→Host
.quiztransfer over the local network (chunked-transfer details deferred to prototype — see Open Questions). - Host UI gates incoming transfers with manual confirm per push — every incoming transfer prompts the operator before being accepted.
- Eager push: Host pushes Client-canvas content + Client resources for the entire quiz to each Client on join. Late-joining Clients see a progress UI and jump directly to the current slide on completion.
- Host is authoritative on timer state; Clients render a local countdown that reconciles with the Host's tick. Time-up emits a "lock" message; late submissions are rejected unless the slide's author-configured rule allows them with a scoring penalty. The host operator can override the timer live (extend, skip, manually lock/unlock).
Schemas and the object-type plugin contract
- Quiz manifest, slide, canvas, and element schemas in the shared class library — see Quiz Package Format.
- Round (slide-grouping) schema.
- WebSocket message envelope schema with extension hooks.
- Schema version field on quiz packages, validation on load.
- Object-type plugin contract per Object-Type Architecture:
IObjectType, Designer editor surface, Host runtime surface, Client runtime surface, optional protocol extension. - Built-in registry per app — types register themselves on startup.
Built-in object types (MVP cohort)
| Type id | Notes |
|---|---|
core.text |
Either canvas. Reference implementation of the plugin contract. |
core.multiple-choice-input |
Client canvas. Tap to submit one option. |
core.free-text-input |
Client canvas. Text-entry, submit answer. |
core.numeric-input |
Client canvas. Numeric validation; closest-wins scoring; essential for tiebreakers and estimation rounds. |
core.timer |
Either canvas. Countdown / elapsed. Foundational to most quiz formats. |
core.leaderboard |
Either canvas. Per-team standings. |
The other seven object types (core.image, core.audio-clip, core.video, core.drawing-input, core.buzzer-input, core.categories-input, core.mini-game) are deferred to Alpha or Beta.
Designer behaviours
F-DE-2 to F-DE-14, F-DE-18, F-DE-19, F-DE-20: create quiz, slides, rounds; place / move / configure elements on both canvases; object-type palette listing the MVP cohort; embedded preview; per-slide and per-round timing and scoring; per-slide host-notes; per-element reveal trigger; save and re-open .quiz files; discover Hosts and push over LAN; canonical .quiz package format.
Out: F-DE-1 (cloud auth), F-DE-15/16 (cloud save/version/trash), F-DE-17 (stylus). All are stretch.
Host behaviours
F-HO-3 to F-HO-17, F-HO-20, F-HO-21: WebSocket server + Bonjour; receive Designer push; load .quiz from disk; offline; resolve object-type registry; start session; join screen showing connected teams; eager push on join; advance through slides; receive answers, score, leaderboard; handle disconnect/reconnect; timer-authority and live override; pair with a Remote and stream mirror + host-notes + state; accept advance/go-back commands from the Remote.
Out: F-HO-1 (auth), F-HO-2 (cloud library), F-HO-24 (rich Remote command set — Alpha).
Client behaviours
F-CL-1 to F-CL-9, restricted to the MVP object-type cohort. One shared device per team; team enters team name; receive eager push; render Client canvas; submit multiple-choice and free-text answers; show team score; reconnect.
Out: drawing input, buzzer input, mini-games — those object types come in Alpha or Beta.
Remote behaviours (minimum viable controls)
F-RE-1 to F-RE-8: the minimum viable Remote controller. Discover Hosts; pair with one; open the control-message-family WebSocket; render a live mirror of the Host's display; show per-slide host-notes; show live session state (scores, timer remaining, slide index); send the core navigation commands (advance, go-back); reconnect after Wi-Fi blips.
Out: F-RE-9 — the rich control command set (jump-to-slide, trigger reveals, lock/unlock Client input, extend/skip timer, override scoring per team) — lands in Alpha alongside F-HO-24. Latency / soak verification of the Remote control loop is also Alpha.
End-to-end vertical slice
- Automated end-to-end test: Designer authors a quiz (with host-notes) → Designer pushes to Host over LAN → Host loads → Clients join (eager push) → quizmaster pairs a Remote → quizmaster advances slides from the Remote → answers tally → leaderboard finalises.
- Manual playthrough of a multi-round quiz (text + multiple-choice + free-text + timer + leaderboard) on a real Wi-Fi network with the quizmaster walking the room with a paired Remote.
Explicitly not in scope
- Media: image, audio, video object types.
- Drawing input, buzzer input.
- Mini-games and the mini-game framework.
- Animated reveals, transitions, big-reveal stings — visual/motion polish is Alpha and Beta.
- Cross-cutting design language application — typography/palette/motion vocabulary per Design Specification is applied in Beta.
- Stylus support — touch only on every platform (OQ resolved).
- Cloud authoring, accounts, version history.
- Any non-functional target requiring real-device perf measurement (those tighten in Alpha and Beta).
- Sign-in flows, app-store distribution, privacy policy, store listings — that's Production.
Acceptance criteria
- Every functional requirement listed under "In scope" passes its automated test.
- The end-to-end vertical slice test runs green in CI on Windows and macOS Designer + Host targets, and runs manually on iPad and Android tablet for Host, plus the four Client platforms and the four Remote platforms.
- A real pub-style quiz of ≥3 rounds, ≥15 slides total, with text + multiple-choice + free-text questions plus a timer and a leaderboard, can be authored in the Designer (including host-notes per slide) and run end-to-end with two or more Client devices and a paired Remote driving advance/go-back.
- The plugin contract is exercised by all five MVP object types, and adding a sixth object type does not require core-code changes (validated by the first Alpha object type).
- No file-system, network, or schema operation depends on cloud or auth.
- The Remote app's minimum viable controls are functional: discovery, pairing, mirror, host-notes display, live state, advance/go-back. The rich command set (F-RE-9 / F-HO-24) is Alpha — the WebSocket control message family must, however, leave room for it without core-code rework.
How success is measured
- Internal demo of the end-to-end slice on every supported platform.
- Time to author a 15-slide quiz from a blank Designer is under one focused sitting.
- A new object type can be added to the codebase by following the plugin contract alone, without touching slide or session code.
Alpha
Goal: Internal testing builds capable of hosting a full quiz. Every built-in object type is in place except the mini-game framework. The Remote app — already shipping in MVP with minimum viable controls — gains its rich control command set. Crash recovery becomes operational. Quality is "developer-acceptable" — the visual/motion polish, cross-cutting design language, and final brand treatment still come in Beta.
The scope shift from MVP is breadth and operational hardening: more object types, richer Remote controls, and the persistence/recovery work that makes a session safe to run for real.
In scope
Object types added in Alpha
| Type id | Why it lands here |
|---|---|
core.image |
Static media is the lowest-risk media type; unlocks image-based rounds. |
core.audio-clip |
Music rounds are a defining pub-quiz format; pairs naturally with core.free-text-input and core.multiple-choice-input. |
core.video |
Builds on core.audio-clip's media-loading work; less common than audio for pub quizzes but completes the media set. |
core.drawing-input |
First object type to introduce a Client→Host live mirror channel; protocol-extension exercise. Touch-only in v1 — stylus support is Stretch. |
core.buzzer-input |
First-press semantics across multiple Client devices; tests fairness and the message round-trip target. |
Each object type implements the full plugin contract (schema, Designer editor surface, Host runtime, Client runtime, optional protocol extension) and ships with an end-to-end test exercising a slide that uses it.
Remote rich control commands
The Remote app itself ships in MVP with minimum viable controls (F-RE-1 through F-RE-8 — discovery, pairing, mirror, host-notes, live state, advance / go-back, reconnect). Alpha layers on the rich control command set — F-RE-9 and the matching F-HO-24:
- Jump to a specific slide (random access, not just sequential).
- Trigger element reveals (e.g. show the leaderboard, show the answer).
- Lock or unlock Client input.
- Extend or skip the timer.
- Override scoring per team.
These are the controls that turn the Remote from "advance the deck" into "fully run the room from your pocket". They land in Alpha because each one cuts across non-trivial Host state (timer authority, scoring rules, element reveal triggers) and benefits from the operational hardening that lands alongside (crash recovery, soak testing).
Designer additions for Alpha
- The object-type palette grows to include the Alpha cohort.
- The media-attachment plumbing (F-DE-8) becomes meaningfully exercised by
core.image,core.audio-clip,core.video. - Team-customisation asset bundling (F-DE-28, F-DE-30) — author attaches buzzer jingles + premade-avatar set to the
.quizunderresources/audio/buzzers/andresources/avatars/. Designer ships a small built-in default asset library of pre-licensed jingles + starter avatars; selections are copied into the active.quizso the package stays self-contained (F-DE-29).
Team customisation at join
Lands alongside core.buzzer-input since the buzzer-jingle pick only makes sense once buzzer slides are playable, and the photo + avatar palette gives the leaderboard something to render the moment any Alpha element runs:
- F-CL-14 — captain takes a photo (or picks from the quiz's bundled premade-avatar set) at join. Camera permission, capture, crop-to-square, JPEG-encode ≤ 64 KB / 256 × 256 px on-Client before upload.
- F-CL-15 — captain picks a buzzer jingle from the quiz's bundled set at join. Preview-on-tap before commit. Picker hides if the quiz bundles no jingles.
- F-HO-26 — Host renders the team photo on leaderboard rows + per-team callouts.
- F-HO-27 — Host plays the per-team buzzer jingle on
core.buzzer-inputfirst-press wins. Operator can mute jingles globally without ending the session.
The team-photo binary travels in the join WebSocket message and lives in the session snapshot for crash recovery; it's cleared at session end (Networking — Team join).
Crash recovery (operational hardening)
This is the load-bearing piece of Alpha — see Networking — Crash recovery and the new requirements:
- F-HO-18: Host persists session state to disk after every scoring event and every slide advance.
- F-HO-19: Host on launch with a saved session prompts to resume or start fresh; resume restores slide pointer, team list, scores, per-element state.
- F-CL-10: Client persists its team identity locally; reconnects to a recovered session as the same team.
The reconnection protocol path is the same one used for Wi-Fi-blip recovery — only difference is whether the Host's state was rebuilt from disk or in-memory. This means the Wi-Fi-recovery work in MVP and the crash-recovery work in Alpha are largely the same code path, with Alpha adding the on-disk snapshot.
Animation and reveal beats
- Animated reveals on Host slides (F-HO-13) — first pass, not yet polished to brand spec.
- Leaderboard reveal animations.
- Question-transition motion budget verified informally (formal target lands in Beta).
Reliability and performance
- Reliability soak test: client disconnect storms, host backgrounding, Wi-Fi flap, simulated Host crash + recovery.
- Performance: 60 fps animation budget verified on iPhone 12 / equivalent Android (measured in the Unity runtime build) per Non-Functional Requirements.
- Element answer-submit round-trip latency < 200 ms on local Wi-Fi (verified).
- Remote control-message round-trip latency < 200 ms on local Wi-Fi (verified).
Documentation
- Object-type plugin contract documented as a knowledgebase page once the contract has stabilised across the Alpha object-type cohort. (Currently described in Object-Type Architecture; the detailed contract page is a build-plan deliverable.)
- Remote control-message family documented in the (planned) Live Play Protocol document.
Explicitly not in scope
- Mini-games and the mini-game framework — Beta.
- Brand-true motion language and full Design Specification treatment — Beta.
- App-store distribution, store listings, privacy policy — Production.
- iPad / Android tablet Designer authoring — Stretch.
- Stylus support — Stretch.
- Multi-Remote support — Stretch, if ever.
- Any cloud or auth.
Acceptance criteria
- A full pub quiz spanning all Alpha object types can be authored, transferred, and run end-to-end without crashes.
- A team can join with a photo (or a premade avatar fallback) and a chosen buzzer jingle; the photo renders on the leaderboard and the jingle plays on the Host when that team wins a buzzer press.
- Reliability soak test passes its acceptance thresholds (no Host crash through a 90-minute session of disconnect storms).
- A simulated Host crash mid-session is recoverable: Host relaunches, the operator chooses "resume", Clients reconnect as the same teams with their scores intact.
- The Remote app, paired with the Host, can drive a full quiz session from the quizmaster's phone using the rich command set — including jumping to a specific slide, triggering a leaderboard reveal mid-slide, and overriding a scoring decision live (all of which build on the MVP advance/go-back baseline).
- Latency targets verified on at least one Client / Host pair and one Remote / Host pair on local Wi-Fi.
- Internal stakeholders (Quiz UK) play a full quiz session and the result is "this could become a product."
How success is measured
- Full internal playthrough — the team can run a quiz night using the platform without manual intervention beyond authoring.
- Bug count after a representative session converges to a fixable list, not a system-level rethink.
- The quizmaster's experience walking the room with the Remote feels right (no constant glances back at the Host).
Beta
Goal: A polished build that Quiz UK can run with real teams in a real venue. Mini-games are introduced. The platform looks and moves like the Design Specification describes, not like a developer build.
The scope shift from Alpha is polish + mini-games — the loud, performative parts of the experience that the brand identity promises.
In scope
Mini-game framework
core.mini-gameelement implementing the full plugin contract.- Mini-game framework as a separate concern within the Client and Host (lifecycle, scoring hand-off, presentation overlay).
- One or two actual mini-games shipped as built-ins. Specific mini-games are a content/build-plan decision, not a knowledgebase one.
Categories question type
core.categories-inputelement implementing the full plugin contract — a defining pub-quiz format ("Name 5 X") with multi-line entry and per-line scoring.
Cross-cutting design language
The full Design Specification is applied across Designer, Host, Client, and Remote:
- Brand palette and gradients.
- Typography per the Design Specification.
- Motion vocabulary — confident/snappy spring motion, big-reveal beats, mascot animation.
- Iconography, layout, spacing scale.
- Voice and tone in copy.
This includes Quiz UK validation — the mascot, branding, and final brand name are confirmed and applied.
Theme system
Two layers of theming, both Beta:
- Designer chrome theme — author preference, light or dark, persisted in app settings (F-DE-25). Authors who spend long sessions in the Designer get a calmer light option.
- Per-quiz theme on Host / Client / Remote — the quiz manifest declares a theme that the playback apps render against (F-DE-26, F-X-4). Default is the dark brand theme.
The two layers are independent. Richer per-quiz custom palettes are Stretch.
Sound design
Full audio language ships in Beta (F-X-5): branded stings (correct / incorrect / lock / time-up / big reveal / end of round / end of quiz), transition motifs, and an optional ambient music bed. Audio is mixed for the venue — stings cut through pub ambient noise without being intrusive. Licensing/sourcing decisions belong to Beta-phase work.
Mascot animation rig
The mascot is rigged once (a single Unity-native rig in the shared assets package) and reused across the Designer, Host, Client, and Remote. Each app picks the appropriate pose from a shared animation library — waving on welcome, cheering on big reveals, sleepy on end-of-quiz.
Per-team theming
A team picks a colour and avatar at join (from a fixed author-configured palette inside the quiz, so brand consistency is preserved). The choice carries through every Client surface, every leaderboard row, and any Host moment that renders per-team identity.
Visual flair beyond the team name. Lands here because Beta is the polish phase — not in MVP/Alpha (where it's deliberately utilitarian), and not Stretch (because it doesn't depend on cloud and adds meaningful UX value to a full Beta playthrough).
Real-device validation
- All target platforms tested on real hardware. Test devices needed: a modern iPhone, an old iPhone, a modern iPad, an old iPad, a modern Android phone, an old Android phone, a modern Android tablet, an old Android tablet. Older devices catch performance regressions on lower-end hardware that the dev workstation hides.
- Performance and latency targets re-verified on the older devices, not just the developer workstation.
- Hardware/OS minimum versions confirmed against Unity's Editor and Player support matrix and against real-device behaviour. The provisional minimums in Non-Functional Requirements become final at the end of Beta.
Quiz UK pilot
- Quiz UK runs at least one real pub quiz night using the Beta build.
- Feedback loop captured (open-questions, bug list, feature requests).
Explicitly not in scope
- App-store distribution, store listings, privacy policy — Production.
- Sign in with Apple — Production, only if iOS App Store distribution is chosen.
- iPad / Android tablet Designer authoring — Stretch.
- Cloud-backed authoring, accounts, libraries — Stretch.
- Stylus support — Stretch.
- The remaining Stretch-tier question types (match-pairs, ranking, hotspot, true-false) — Stretch.
- AI-aided quiz authoring, broadcast view, recurring teams, tournaments, templates, question bank — Stretch.
Acceptance criteria
- A real Quiz UK pub night runs successfully on the Beta build.
- The Designer, Host, Client, and Remote each visually match the Design Specification on every supported platform (cross-platform polish is part of Beta, not deferred to per-platform).
- Mini-games run in-session without breaking the per-slide round-trip latency budget.
- Performance budget (60 fps; transition < 500 ms; round-trip < 200 ms) holds on the older test devices.
How success is measured
- Quiz UK is willing to run more nights with the Beta build.
- Real participants don't notice it's a beta.
Production
Goal: A launch build distributed through real channels to real users — quizmasters beyond Quiz UK. Polish is locked; the remaining work is the commercial and operational layer.
The scope shift from Beta is launch readiness: distribution, identity, store listings, privacy, and operational posture.
In scope
Distribution
Confirmed distribution channels:
- iOS App Store — required for any iPhone/iPad distribution. Triggers Sign in with Apple obligation only when cloud auth ships in Stretch; v1 has no auth.
- Google Play Store — required for any mainstream Android distribution.
- Microsoft Store — primary discoverability channel for Windows. Sideloaded
.exe/.msimay be considered later for power users. - macOS App Store / direct DMG — both. App Store buys discoverability and one-click install; direct DMG is the lower-friction path for users who prefer to download from the project site.
Each channel adds work for: signing, packaging, submission flow, and update mechanism.
Identity and policy
- Sign in with Apple wired up if iOS App Store cloud-backed auth (a Stretch goal) ships first. Apple requires Sign in with Apple only when other social-auth providers are offered, so it's only relevant when cloud auth lands. v1 ships no auth on any app.
- Privacy policy drafted and published.
- Store listings drafted (descriptions, screenshots, age rating, accessibility statements).
Hardware and OS minimums
The provisional minimums in Non-Functional Requirements are finalised in Beta, not Production. By Production they are locked. Production work is to ensure each store listing accurately reflects the locked minimums and that the app refuses to run (with a clear message) below them.
Operational
- Performance and latency targets re-verified on the production builds, not just the development builds.
- Crash reporting / telemetry decisions made (consistent with the privacy posture in v1 — see Non-Functional Requirements).
Explicitly not in scope
- Cloud-backed authoring, accounts, libraries — Stretch.
- Bundle-supplied object types — Stretch.
- Internet-based live play — Stretch.
- iPad / Android tablet Designer authoring — Stretch.
- Anything in the Stretch list.
Acceptance criteria
- A quizmaster who is not Quiz UK can install, author, and run a quiz on every supported channel (iOS App Store, Google Play, Microsoft Store, macOS App Store, macOS DMG).
- All store reviews / approvals pass.
- Performance and latency targets hold on the production builds, not just the development builds.
- Privacy policy and store listings are live and accurate.
- The app refuses to install or shows a clear "device not supported" message on devices below the locked OS minimums.
How success is measured
- Real third-party quizmasters (beyond Quiz UK) successfully run quiz nights using the production build.
- Crash-free session rate above an agreed threshold across the supported devices.
Process
Project Management
Project management runs in Azure DevOps Boards, in the same project that hosts the code repository and CI / CD pipelines.
Workflow from PRD sign-off to delivery
- PRD sign-off. The PRD is the locked-in requirements artefact, generated from the knowledgebase by the
docsskill. Sign-off ends "what are we building" debate; subsequent changes go through a controlled change process (re-edit the knowledgebase, regenerate, re-sign-off). - Decompose into Epics + Features. Each major area of the PRD becomes one Epic in ADO. Features sit under Epics and represent shippable user-facing capabilities.
- Decompose Features into Stories + Issues. Stories are user-shaped slices of a Feature; Issues are technical or bug-fix items inside a Story or directly under a Feature. Sprint-sized.
- Sprint planning. The team commits to Stories / Issues for the upcoming sprint based on capacity and priority. The sprint backlog is the day-to-day work queue.
- Sprint execution. Work flows across the board columns below. Daily standup checks for blockers and surfaces "Ready For Test" work to QA.
- Sprint review + retro. End-of-sprint demo of "Done" work; retrospective captures process improvements.
Work-item hierarchy
Epic
└── Feature
├── Story
│ └── Issue (task-level)
└── Issue (standalone)
| Level | Used for | Example |
|---|---|---|
| Epic | A major area of the PRD that takes multiple sprints. | "Designer authoring features (MVP)" |
| Feature | A shippable user-facing capability. | "Designer can save and reopen .quiz files" |
| Story | A user-shaped slice of a Feature, sprint-sized. | "As an author I can save my quiz to disk so I don't lose my work." |
| Issue | A technical task or bug. May sit under a Story or directly under a Feature. | "Implement atomic write protocol with <target>.tmp → fsync → rename" |
Definition of done lives on each Story / Issue per the Testing and development-standards conventions: failing test first, end-to-end automated test for user-facing behaviour, lint + format pass, code reviewed.
Board columns
The sprint board has six columns. Work moves left-to-right. No column other than "To Do" is allowed to grow unbounded — WIP limits are enforced informally during standup.
| Column | Meaning | Entry criteria | Exit criteria |
|---|---|---|---|
| To Do | Committed for the sprint, not yet started. | Estimated, has acceptance criteria. | Picked up by an engineer. |
| Blocked | In flight but cannot progress without external action. | Blocking issue identified and assigned an owner. | Block resolved — moves back to whichever column it came from. |
| In Development | Engineer is actively working on it. | Branch created, work in progress. | Code complete, PR opened, CI green. |
| Ready For Test | Code merged or ready to merge; needs verification. | PR merged to main (or behind a feature flag) and CI green. |
Picked up by a tester. |
| In Testing | A tester is exercising the change end-to-end on real platforms. | Tester assigned. | All test cases pass on every target platform; bugs (if any) become new Issues that block reopening. |
| Done | Verified and shipped to the relevant phase build. | All acceptance criteria + Definition of Done met; verified in staging / preview build. | — |
"Blocked" is a separate column from "In Development" so blockers are visible at-a-glance during standup. Items don't sit in "Blocked" without an assigned owner driving the unblock.
PRD-to-Board mapping
When the PRD is signed off, the work is broken down following the hierarchy above. Build-plan sections map cleanly:
- A
## Sectionin the Build Plan tagged[MVP]designer → typically becomes one Epic ("Designer MVP — section title") or one Feature under a broader Epic, depending on size. - A
- [ ] itemin the build plan → typically becomes one Story or one Issue, depending on user-visibility.
The build plan and the ADO board are kept in sync: completing a Story / Issue in ADO ticks the corresponding - [ ] in the build plan; adding a new build-plan item adds a corresponding ADO work item. The build plan is the source of truth for sprint-able scope; the ADO board is the source of truth for in-flight status.
Cadence
- Sprint length: TBD by the team at project kickoff (two-week sprints are the default candidate).
- Daily standup: short, focuses on the board state — what's "In Testing", what's "Blocked", what's "Ready For Test" awaiting a tester.
- Sprint planning: at the start of each sprint, pulling from the prioritised backlog.
- Sprint review + retro: at the end of each sprint.
Branching convention
Every change lands on a short-lived branch off main and merges back via PR. Branch names tie back to the work item driving the change:
- Feature branches:
feature/{work-item-id}-{short-description}— e.g.feature/1234-multi-select-touch-parity. - Bug-fix branches:
bugfix/{work-item-id}-{short-description}— e.g.bugfix/1789-autosave-race-on-quit.
{work-item-id} is the ADO work-item id (Story / Issue) the branch addresses. {short-description} is a kebab-case slug of three to five words capturing the gist. Branches stay short-lived: created when work starts, merged when "Done", deleted on merge. PR titles include the work-item id so the link is bidirectional.
Tooling notes
- ADO Boards plays well with the ADO wiki — the rendered knowledgebase mirror is published to the wiki, so reviewers can link from a work item to the requirement it implements without leaving ADO.
- The wiki mirror is regenerated by the
docsskill; see that skill for the publish pipeline.
Reference
Open Questions
Decisions deferred until more is known. Each entry says what is open and the reason it is being deferred.
Each entry is tagged with its status:
- Prototype-deferred — answer comes out of the load-bearing prototype work. No user-input decision needed today.
- Phase-deferred — answer is committed to a future phase (most often Beta or Production) where the work belongs.
- Open — needs a decision, currently waiting on external input or a deliberate timing.
-
Bundle-supplied object types (Phase-deferred — Stretch). v1 ships built-ins-only;
.quizpackages contain no runtime C# code. A future stretch goal — alongside cloud-backed authoring — is to allow object types bundled inside a.quizpackage as additional C# behaviours + prefabs. That brings real questions: signing/verification of bundled modules, sandboxing their C# behaviours, the author-facing UX for installing third-party object types, and whether the loader is exposed to end users at all. Tracked in Stretch. -
Slide schema specification (Prototype-deferred). The exact field shape of a slide JSON file (host_canvas, client_canvas, element coordinates / region anchors, timing, scoring overrides, host-notes, per-element reveal trigger) needs to be pinned down in the shared C# class library. The Quiz Package Format describes the model; the field-by-field schema is build-plan work in MVP.
-
Internet-based live play relay (Phase-deferred — Stretch). Cloudflare Durable Objects are the leading candidate but the protocol design and discovery mechanism are not yet specified. Tracked in Stretch.
-
Cross-quiz analytics (Phase-deferred — Stretch). Whether and how to record quiz session data for the author's later review (e.g. "which slides were hardest?"). Privacy posture would need to be designed alongside. Tracked in Stretch.
-
Embedded WebSocket server library (Resolved).
SimpleWebTransport(James-Frowen, MIT, active 2026 — v3.1.0). Picked 2026-05-11 after a research round comparing it against dormantwebsocket-sharpforks, modern but Unity-incompatible ASP.NET Core libraries (SuperSocket 2.0, Watson Webserver), and Unity's broken built-inHttpListener.AcceptWebSocketAsync. SimpleWebTransport runs as both server and client in Unity-IL2CPP, is used by Mirror Networking (24k+ stars) in production, and bypasses Unity's HttpListener gap with its own TCP-based WebSocket impl. The Designer (.NET MAUI Blazor Hybrid) uses bareSystem.Net.WebSockets.ClientWebSocketfrom the BCL — no third-party library on the Designer side. See Tech Stack. -
mDNS library and per-platform bridges (Resolved).
Makaretu.Dns.Multicast.New(active fork ofrichardschneider/net-mdns, MIT, latest release May 2025). Picked 2026-05-11. Pure C#, supports both advertisement (Host) and discovery (Client / Designer / Remote), targets .NET Standard 2.0 + .NET 8 + .NET 9. Native iOS (NSNetService) and Android (NsdManager) bridges are documented as fallback if Alpha-prototype validation reveals reliability issues with the pure-C# multicast on those platforms — primary path is the single C# library. See Tech Stack. -
Designer→Host transfer wire-level details (Resolved). Picked 2026-05-11: 64 KB chunks, push-progress per chunk, CRC32 per chunk, resume-from-offset on reconnect, no extra wire compression (
.quizis already zip-compressed). The framing schema lives in the (planned) Live Play Protocol document; defaults pinned here keep the design space narrow during prototype work. See Networking — Designer→Host package transfer. -
Eager-push very-large-package fallback (Resolved). Picked 2026-05-11: always eager-push; no streaming fallback. Maximum
.quizpackage size cut from 500 MB to 200 MB to keep eager-push viable on older mobile devices (2–3 GB RAM). Authors who need more than 200 MB of media are out of scope for v1. See Quiz Package Format. -
Remote pairing UX (Resolved). Both options ship — the Host shows a short numeric pairing code and a QR; the Remote can either type the code manually or scan. Picked 2026-05-11. See F-RE-2, Host surfaces — Remote pairing, Remote surfaces — Discover + pair.
-
Final brand and product name (Phase-deferred — Beta). The apps are referred to by their engineering project names (Quiz.Designer, Quiz.Host, Quiz.Client, Quiz.Remote) through MVP and Alpha. The Quiz UK pilot in Beta is the moment to lock the final brand and product name, alongside store-listing prep and brand-true polish. See Design Specification.
-
Per-platform camera-capture API (Resolved). Unity
WebCamTexture— single cross-platform API. Picked 2026-05-11. Skip native pickers + camera-roll access for v1; covers our minimum (capture frame → save as JPEG). Re-evaluate in Beta if UX feedback demands a native picker. See F-CL-14. -
Team-photo size + transmit budget (Resolved). Picked 2026-05-11: 256 × 256 px, JPEG quality 75, 96 KB hard cap (re-encode at quality 60 if first pass exceeds the cap). Centre-crop, bilinear downscale. Belongs in the (planned) Live Play Protocol document alongside the join-message envelope schema. See F-CL-14, Networking — Team join.
-
Designer default asset library — licensing + content (Resolved). Picked 2026-05-11: CC0 / public domain only (Freesound CC0 buzzers, OpenGameArt CC0 avatars — zero attribution friction, distributable inside any author-shipped
.quiz). 10 buzzers + 24 premade avatars in the v1 library. Library updates ride app releases — no content endpoint, no auth, no infrastructure in v1. See Designer Shell — Default asset library, F-DE-29, F-DE-30.
Glossary
| Term | Definition |
|---|---|
| Author | The person who creates a quiz using the Designer. |
| Designer | The authoring app, used on Windows or macOS desktop. (iPad / Android tablet authoring is Stretch.) |
| Host | The app that runs the live quiz session, typically on a device connected to a projector or TV. Runs on Windows, macOS, iPad, and Android tablet. |
| Client | The team app, used on iPhone, Android phone, iPad, or Android tablet. One shared device per team. |
| Remote | The quizmaster's pocket controller, runs on phone or tablet. Pairs with one Host; mirrors the Host display, shows the per-slide host-notes, and sends control messages. Ships in MVP with minimum viable controls (advance / go-back); the rich control commands (trigger reveals, lock/unlock input, extend/skip timer, override scoring, jump-to-slide) land in Alpha. |
| Quizmaster | The person running the live session at the venue. Operates the Host directly and optionally a paired Remote. |
| Host-notes | Free-form per-slide text the author writes in the Designer for the quizmaster — hints, answer keys, presentation cues. Visible only on the Remote during play; never on the Host canvas or any Client. |
| Participant / Quiz Goer | A person playing the quiz. v1 has no per-individual representation; participants are organised into Teams that share a Client device. |
| Team | The unit of play in v1. One Team = one shared Client device + one team name. All scoring, answers, and standings are per-Team. |
| Slide | The unit of presentation in a quiz, like a PowerPoint slide. Each slide has its own Host canvas and Client canvas. |
| Round | A grouping of contiguous slides sharing a theme, scoring rule, or section title — a "section" in PowerPoint terms. Rounds are optional metadata over the slide list, not the unit of presentation. |
| Host canvas | The TV/projector-facing surface of a slide. A fixed virtual canvas (1920×1080 baseline) that scales to fit the connected display. |
| Client canvas | The phone/tablet-facing surface of a slide. A responsive layout (anchors / regions / stacks) that adapts across phone, tablet, portrait, and landscape. |
| Element | A placed instance of an object type on a canvas, with its own per-instance properties. |
| Object type | A self-contained module that defines an addable element — its schema, Designer editor surface, Host runtime surface, Client runtime surface, and any messages it exchanges. New object types can be added without modifying core code. See Object-Type Architecture. |
| Round type | An informal term for a recognisable composition of slides and object types (e.g. "music round", "drawing round"). Not a load-bearing schema concept under the slide model — recipes, not primitives. |
Quiz package / .quiz file |
Zip archive containing the manifest, slide definitions, optional bundled object types, and resources for a quiz. See Quiz Package Format. |
| Live play | The act of running a quiz with a Host and connected Clients. |
Stretch
Stretch
Features beyond the initial launch. Worth noting down so the ideas aren't lost, not planned for a specific release. Scope and acceptance criteria for a Stretch item are defined when the item is promoted to a real phase, not now.
Web-based Designer
A browser-based authoring app — alternate producer of the same .quiz package format the desktop / iPad MAUI Blazor Designer writes today. Host / Client / Remote are unchanged: they consume .quiz files regardless of which Designer produced them.
Big simplification vs the previously-considered Angular path: the web Designer reuses the same Razor component library the desktop / iPad MAUI Blazor Designer is built from. Single component library, three deployment targets (MAUI desktop, MAUI iPad, Blazor WASM web). No fork, no codegen, no TypeScript.
Stack:
- Frontend — Blazor WASM (.NET in the browser). Reuses every Razor component from the MAUI Blazor Designer. Slide preview = same
Quiz.PreviewUnity WebGL build the desktop Designer ships, embedded as<canvas>directly in the Blazor page (noBlazorWebViewwrapper needed in the browser host). - Backend — ASP.NET Core Web API (.NET 8/9). References the same
Quiz.Core(.NET Standard 2.1) library every other consumer uses, for server-side schema validation and.quizpackage generation. Postgres for quiz drafts + accounts. CDN for static assets (Unity WebGL bundle, asset bundles). Optional Redis for cache / SignalR for real-time collab. - Deployment — containerised; Kubernetes for autoscaling stateless API pods. Postgres managed (RDS / CloudSQL / Azure DB). No GPU pods needed — Unity WebGL renders client-side.
Code reuse:
- Razor component library — every authoring screen the desktop / iPad MAUI Designer ships is a Razor component. Blazor WASM consumes the same components. The MAUI shell vs the browser host differ in chrome only (multi-window / sheets vs single-page-in-a-tab).
Quiz.Core(.NET Standard 2.1) — shared by every consumer (MAUI Designer + Web API + every Unity project + Blazor WASM running in the browser).Quiz.Preview— same Unity WebGL build the desktop / iPad Designer embeds. Served from the CDN; loaded into the browser canvas.- Single C# / .NET stack across the entire authoring surface. No TypeScript, no Angular, no schema codegen pipeline.
Why this is now small: with the MAUI Blazor commit, the web Designer is mostly a deployment target for an existing component library — the heavy lift is the backend (auth, multi-tenant Postgres, CDN, k8s ops), not the UI.
Risks specific to this stretch:
- Cloud infrastructure scope — Postgres, k8s, CDN, ops on-call. Material commitment; gates the stretch promotion.
- Auth / multi-tenant SaaS plumbing — OAuth → JWT, RLS or app-layer tenant isolation. Standard but real work.
- Real-time collab (optional) — SignalR + a CRDT layer if multi-user editing is required. Defer unless asked for.
- WebGL bundle size in browser — 20-50MB initial download; CDN + brotli/gzip + lazy-load until preview pane opens.
- Asset bundle CORS / signed URLs — backend serves them with correct headers and per-tenant access controls.
- Browser compat — Unity WebGL safest on Chromium; Firefox / Safari work with caveats.
- Razor portability assumption — most components run unchanged on Blazor WASM, but native-only chrome (file pickers) needs MAUI-vs-WASM conditionals.
Spikes (when stretch is promoted):
- Blazor WASM bootstrap of a subset of the Razor component library — confirm components render and behave equivalently to the MAUI host.
Quiz.Preview(WebGL) embedded directly in a Blazor WASM page (noBlazorWebView); JS bridge round-trip.- ASP.NET Core API skeleton in k8s — minimal API + Postgres + CDN + CI deploy.
- Auth + multi-tenant story — OAuth login, per-tenant
.quizstorage, isolation enforced.
Cloud-backed authoring
The single biggest stretch goal. Adds a cloud account with authentication and storage — quizmasters sign in, save quizzes to the cloud, get a versioned library, and download quizzes onto a Host. Vendor (managed Postgres + S3-compatible object storage) decided when this stretch is promoted; the architecture stays vendor-neutral until that point.
- Provision the cloud backend (vendor TBD).
- Account / profile + quiz library data store with per-tenant isolation enforced (RLS or equivalent).
- Quiz blob storage with per-owner isolation.
- Version history with pruning policy (latest N + pinned).
- Sign-in on Designer (F-DE-1) and Host (F-HO-1) per Authentication.
- Designer: save quiz to cloud, version history, restore (F-DE-15).
- Designer: soft-delete + trash view (F-DE-16).
- Designer: pin a version to protect from pruning.
- Host: list quizzes from the cloud library, mark which are downloaded (F-HO-2).
- Host: download and cache a quiz package from the cloud (F-HO-2).
Bundle-supplied object types
- Loader for object types bundled inside a
.quiz(signing, sandboxing, install UX) — see OQ#1. - Author-facing UX for installing third-party object types.
This is gated on cloud-backed authoring landing first because the trust / distribution / install model only really makes sense in a cloud-aware world.
Stylus support
- Apple Pencil pressure / tilt / palm rejection on iPad.
- S Pen, Surface Pen, Wacom on Android / Windows tablets through Unity's Input System.
- Applies to: Designer authoring sketches, embedded preview,
core.drawing-inputelement on the Client.
v1 is touch-only on every platform.
iPad / Android tablet Designer
The Designer ships first-class desktop only in v1 (Windows + macOS). iPad and Android tablet authoring is deferred to Stretch and ships derived from the Web-based Designer codebase — same Razor component library wrapped as a mobile app shell. Drives the iPad / Android tablet authoring path off the same WebGL-in-browser preview the web tool uses, sidestepping the WebGL-in-WKWebView risk that would otherwise apply to a MAUI iPad target. Ships only after the web-based Designer is in place.
Real-time collaborative quiz authoring
Google-Docs-style multi-author editing of a single quiz. Depends on cloud-backed authoring + a CRDT layer (e.g. Yjs equivalent) over the quiz model. Adds significant infrastructure complexity and is deferred until clear demand from quizmasters working in teams.
Public quiz marketplace
A discovery + sharing surface where authors can publish quizzes for others to clone or play. Distinct from internal team sharing: this is public, with reputation, ratings, and possibly paid distribution. Depends on cloud-backed authoring. Deferred until cloud-backed authoring is in place and a clear product proposition forms around third-party sharing.
Per-individual identity within a team
The resolved decision is one shared Client device per team. Per-individual play (each team member on their own Client, scoring rolls up to the team) is a possible future expansion. Reopening would change the eager-push model and the join screen — not a small change. Worth keeping on the backlog so the idea isn't forgotten.
Multi-Remote (co-quizmasters)
v1 supports zero or one Remote per session. Multiple Remotes paired to the same Host (e.g. a quizmaster and a scorekeeper on separate phones) is a possible future expansion. Adds protocol questions: who can override whom, what happens when two Remotes try to advance simultaneously. Defer until anyone asks.
Cross-quiz analytics
Recording quiz session data for the author's later review — "which slides were hardest?" "which round dragged?" — see OQ#4. Privacy posture would need to be designed alongside the feature.
Internet-based live play
An optional relay (Cloudflare Durable Objects or similar) that proxies WebSocket traffic between Host and Clients in different locations. Same protocol; different transport. Discovery shifts from Bonjour to a host-issued session code (F-X-6). The join / live-play message families are identical between LAN and internet paths; only the transport differs. UI surfaces this brings in:
- Client —
client.screen.code-entrypanel with a chunked code input (Q7-3K9-FX), reachable from the discover screen as an alternative to picking a Host from the Bonjour list. See Client surfaces. - Host —
host.idle.session-codeandhost.join.session-codesurfaces displaying the relay-issued code alongside the LAN QR. See Host surfaces. - Remote — existing pair-method picker (Remote surfaces — §2) gains an internet branch the same way the Client discover screen does.
See Networking — Internet-based play and OQ#3 for the open relay-protocol design questions.
Better Host crash recovery
Crash recovery is in v1 (Alpha phase). Stretch ambition: faster snapshot cadence (per-message rather than per-event), fully resumable timer state including elapsed time during a Host outage, multi-instance Host failover. None of these are required for a workable v1.
Additional question types
The v1 object-type catalogue ships 13 built-ins across MVP, Alpha, and Beta. The candidates below extend the catalogue with formats that are distinct enough to warrant their own object-type modules but didn't make the v1 cut. Each implements the full plugin contract.
| Type id | Why it earns its own type | Typical placement |
|---|---|---|
core.match-pairs-input |
Drag/connect UX. Multi-pair scoring (each correct pairing scores). | Client canvas. |
core.ranking-input |
Drag-to-reorder UX. Scoring can be all-or-nothing or partial-credit per item-in-correct-position. | Client canvas. |
core.hotspot-input |
Tap on image coordinates. "Where on the map is X?" / anatomy / geography. | Client canvas. |
core.true-false-input |
Visually distinct from multiple-choice (✓ / ✗ vs A / B). Lower priority — multiple-choice covers the format functionally. | Client canvas. |
AI-aided quiz authoring
LLM-backed assistance inside the Designer: generate questions on a topic, suggest distractors for a multiple-choice question, propose a difficulty rating, draft host-notes from a question and its answer.
The hard part isn't the integration — it's verification. LLMs hallucinate trivia answers regularly, so the UX must surface the model's confidence, cite sources where possible, and require the author to confirm each generated artefact before it lands in the quiz. Treating LLM output as a starting draft (always edited, never blindly accepted) is the load-bearing UX choice.
Privacy posture decisions: whether prompts and generated content stay local-only, are sent to a third-party API, or run via a local model.
Broadcast / streaming-friendly Host view
A dedicated Host display mode optimised for streaming. The default Host display is designed for a TV in a venue; a streaming view tones down screen-burn elements (no large solid backgrounds), boosts overlay readability, and crops out chrome that's only useful in-room (e.g. join-code prompts after the quiz starts).
Goal: a quizmaster can drive an OBS-quality stream by capturing the Host window directly — no separate OBS overlay, no second-monitor wrangling. Notes from the original proposal review flagged "OBS as a hard requirement" as a barrier; this absorbs that into the Host app.
Recurring teams across sessions
Persistent team identity across multiple quiz nights. A team that played last week shows up tonight with their previous name, colour, and (optionally) a season-long score history.
Depends on:
- Cloud auth (the Quizmaster's account anchors the league of recurring teams).
- A team-identity store keyed by quizmaster + venue.
- Team-claim UX on Client join: "Are you returning team X?"
Privacy and data-protection requirements need design alongside — see OQ#4 for the analogous concern with cross-quiz analytics.
Tournaments / leagues
A series of quiz sessions feeding a season-long aggregate leaderboard. Distinct from a single quiz: needs season schema, cross-session scoring rules, league standings UI on the Designer/Host. Depends on recurring teams and cloud-backed authoring landing first.
Quiz templates / starter packs
A library of pre-built quizzes the author can clone and adapt. Lowers the barrier to a first-night quiz — start from "Pub Quiz Classic" rather than a blank slide.
Depends on cloud-backed authoring (templates live in the cloud library; cloning makes a private copy for the author to edit).
Question bank / reusable questions
The author's personal library of questions, decoupled from any specific quiz. A question can be authored once and pulled into any number of quizzes. Each question carries its own answer, scoring rule, and metadata.
Depends on cloud-backed authoring (question bank lives in the cloud library, not a single .quiz package).
Speed-round mode
A round-level config that runs slides back-to-back with very short timers and no inter-slide pause. Different cadence to the default per-slide pacing — pressure-format rounds.
Could be promoted to Alpha as a small addition to the round-level timing schema rather than waiting for Stretch — it's mostly configuration on top of existing timer and round primitives. Logged here so it isn't lost.