Skip to content

Functional Requirements

Sharp Ninja edited this page Oct 31, 2026 · 6 revisions

Functional Requirements (MCP Server)

FR-ACCESSCONTROL-024 Moderator and administrator access control

Allow authorized administrators to grant and revoke moderator, senior-moderator and administrator rights; bootstrap explicit existing profile IDs from server configuration. Scope: layer-1+ Acceptance Criteria:

  • WORDPAIRS_ADMIN_PROFILE_IDS is a private server-only comma-separated allowlist of stable existing profile IDs. Empty configuration grants no bootstrap authority and aliases/browser input cannot grant access. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Moderators moderate and curate; senior moderators additionally resolve independent appeals; administrators additionally manage users. Proposer/original-reviewer independence remains enforced. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Roles are read from trusted current server/database evidence; configured administrator authority cannot be revoked in the UI and the final effective administrator cannot be removed, including concurrent cross-revocation. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Role changes atomically persist revision, canonical retry result and reasoned audit. Revoked administrators cannot replay old grants to regain authority; directories omit credentials, emails and OIDC subjects. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)

FR-ADMINPAGES-023 Administration pages

Provide owner-only GitHub administration at /admin and /admin/words; public guest play and inactive role grants cannot authorize access. Keep immutable, audited content commands and safe retries. Scope: layer-1+ Acceptance Criteria:

  • Authorized staff can search/filter the moderation queue at /admin, approve/reject with reasons, review history and independently resolve permitted appeals. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • /admin/words supports audited immutable word/hint editing. User delegation is disabled: /admin/users redirects to /admin and its API returns404 without reading/writing access storage. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Ordinary players cannot fetch privileged records or perform administrative writes; stale authorized GET responses cannot restore private data after an authorization denial. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Phone-sized forms and controls remain readable and operable; uncertain-write retries preserve command identity and clear successful drafts without duplicates. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Only the verified GitHub owner admin session sees moderation and word/hint navigation. Public guest sessions see no admin links; no session sees user-delegation links. Refresh failure and logout remove privileged links. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Only GitHub numeric account16146732 with login sharpninja can establish an admin identity. Authorization Code uses exact registered callbacks, S256PKCE, cryptographic state and a one-time expiring browser-bound challenge; wrong users, duplicate parameters, replay and provider failures issue no admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Guest/admin cookies and stored challenge/session digests use separate namespaces. Copying a token into the other cookie cannot authenticate. GitHub login/logout preserves the guest session. Admin API responses are private/no-store and logout requires same origin and revokes the stored admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Navigation/private content require the verified admin session; ordinary guest grants cannot authorize admin pages. Callback failure gives safe visible feedback; logout/failure removes links, stale requests cannot restore private state, and active content edits retain audit/version/retry rules. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)

FR-ANCHORSELECTION-007 Category selection

At game start, the service snapshots the active roster in roster order and indexes players from 1 through N. It draws a random integer from 1 through N, inclusive; the player with the matching index becomes the anchor and chooses the first round’s category. The index remains stable for the match. After each round, the first correct solver chooses the next category. The game uses the earliest accepted correct submission to identify that player. Scope: layer-1+ Acceptance Criteria:

  • Given a new game with 2-10 players, when the game indexes the start roster from 1 through N and draws a random integer from 1 through N inclusive, then the player with that index becomes anchor; when that player chooses a category, the first round uses the chosen category and its background. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given at least one player solves a round, when the round ends, then the earliest accepted correct solver can select the next category. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • No response and no solver: A missing answer counts as unsolved and advances to the next hint. After Hint 3 deadline the round reveals and awards zero to remaining players. If nobody solves, choose the next category uniformly from the six configured categories. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-ANSWERREVEAL-012 Round reveal

When a round ends, the game reveals the correct paired answer. Players who remain unsolved after Hint 3 receive zero points for that round. Scope: layer-1+ Acceptance Criteria:

  • Given a player remains unsolved after Hint 3, when the round ends, then the player earns 0 points and the game reveals the correct answer. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an unsolved player submits an answer for the current stage, when the server checks it against the target and approved accepted-answer list, then only a correct answer is accepted and scored once for that stage; an incorrect answer does not change score or expose guess text to other players; and the target answer remains unavailable to clients until round reveal. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • No response and no solver: A missing answer counts as unsolved and advances to the next hint. After Hint 3 deadline the round reveals and awards zero to remaining players. If nobody solves, choose the next category uniformly from the six configured categories. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-ANSWERSTAGES-008 Answer stages

The first answer stage has no hint. Each unsolved player gets one submission per stage. Once all unsolved players submit, the game confirms stage results and reveals the next predefined hint only to those who remain unsolved. This repeats for up to three hints. Scope: layer-1+ Acceptance Criteria:

  • Given a player submits the correct answer during the no-hint stage, when the game accepts it, then the player earns 5 points and their round score locks. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player solves after Hint 1, Hint 2, or Hint 3, when the game accepts the answer, then the player earns 3, 2, or 1 point respectively. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player remains unsolved after Hint 3, when the round ends, then the player earns 0 points and the game reveals the correct answer. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given multiple unsolved players, when all unsolved players submit for the current stage, then the game confirms stage results and shows the next hint only to players still unsolved. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an unsolved player submits an answer for the current stage, when the server checks it against the target and approved accepted-answer list, then only a correct answer is accepted and scored once for that stage; an incorrect answer does not change score or expose guess text to other players; and the target answer remains unavailable to clients until round reveal. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Match length and stage timer: Eight rounds. Each of the four answer stages has a 20-second deadline; a stage closes early when all unsolved players submit. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • No response and no solver: A missing answer counts as unsolved and advances to the next hint. After Hint 3 deadline the round reveals and awards zero to remaining players. If nobody solves, choose the next category uniformly from the six configured categories. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-CANDIDATEHOLD-028 Hold and resume nomination review

Allow independent moderators to pause pending nomination review without losing counts or implying approval. Scope: layer-1+ Acceptance Criteria:

  • An independent moderator with a nonempty reason can move a pending candidate to on_hold; unauthorized actors and the proposer cannot do so. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • on_hold retains nominations and history but prevents approval/publication/playability and never changes current scores or progression. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • An independent moderator resumes with a reason to pending_review after rechecking unique-count threshold; hold/resume actor and reasons are audited. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)

FR-CATEGORIES-005 Relationship categories

The initial category set supports Heroes and Villains, Colors, Year and Event, Director and Movie, Singer and Song, and Opposites. Each pair belongs to one category. Scope: layer-1+ Acceptance Criteria:

  • Given a new game with 2-10 players, when the game indexes the start roster from 1 through N and draws a random integer from 1 through N inclusive, then the player with that index becomes anchor; when that player chooses a category, the first round uses the chosen category and its background. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given two word pairs in the same category, when each is played, then both rounds use that category’s predefined background. A pair in another category uses that category’s background. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a category is enabled, when the content catalog is checked, then every playable pair has a prompt, target answer, three ordered hints, accepted-answer policy, and category background. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Colors category: Each Colors pair contains complementary colors. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-CATEGORYART-006 Category backgrounds

Each category maps to one predefined background asset. All word pairs in the same category use that background. The background must remain legible behind the prompt, hints, answer field, and scorecard. The initial six category illustrations use cel-shaded comic panels; gameplay content rests on a readable frosted translucent panel. Artwork fills the page background, not a separate artwork panel. Each category provides 9:16 and 16:9 compositions selected by viewport orientation. Scope: layer-1+ Acceptance Criteria:

  • Given two word pairs in the same category, when each is played, then both rounds use that category’s predefined background. A pair in another category uses that category’s background. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player uses a phone-sized viewport in portrait or landscape, when they operate primary game actions with touch or keyboard and view text over category backgrounds, then the actions remain operable, text and controls retain clear contrast, focus is visible, and selected icon, solved status, and score changes are indicated by labels or shapes as well as color. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • All six initial categories use distinct category-themed cel-shaded comic illustrations with bold ink outlines, flat colors, hard-edged shadows and halftone accents, rather than abstract placeholders. (evidence: docs/evidence/category-artwork/receipt.json; docs/category-artwork-prompts.json; tests/browser/accessibility.phase6.spec.ts. Production Chromium22/22; all12 exact-aspect assets independently inspected. Emulated viewports, not physical-device or liveRealtime certification.)
  • Category artwork is a full-page background, not an image panel. Gameplay content uses a frosted translucent panel that lets the background show softly without distraction; text and controls remain readable on phone and desktop and when artwork fails. (evidence: docs/evidence/category-artwork/receipt.json; docs/category-artwork-prompts.json; tests/browser/accessibility.phase6.spec.ts. Production Chromium22/22; all12 exact-aspect assets independently inspected. Emulated viewports, not physical-device or liveRealtime certification.)
  • Each of the six category backgrounds has an independently recomposed 9:16 portrait version and 16:9 landscape version; the game selects the matching orientation and updates it when the viewport orientation changes. (evidence: docs/evidence/category-artwork/receipt.json; docs/category-artwork-prompts.json; tests/browser/accessibility.phase6.spec.ts. Production Chromium22/22; all12 exact-aspect assets independently inspected. Emulated viewports, not physical-device or liveRealtime certification.)

FR-CATEGORYSUGGESTIONS-017 Category and pair suggestions

After a player profile has won 10 games over its lifetime, the player can have one open suggestion at a time: one new relationship category with exactly three word pairs. Suggestions enter review and moderation; approval does not bypass the existing curated-content requirements. Approved pairs become eligible for future games only after their complete content records and category background are ready. Scope: layer-1+ Acceptance Criteria:

  • Given a player has fewer than 10 game wins, when they open the content suggestion flow, then category-and-pair submission is unavailable. Given they have won 10 games, then they can submit one proposed relationship category with three word pairs for review. The submission is not playable until approved and complete. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an actor is not authorized to perform moderation or catalog activation, when they attempt to change moderation status or publish content, then the service rejects the action; every change to nomination count, moderation status, and eligibility is auditable; and content cannot be selected for play until it has passed moderation and is complete in the curated catalog. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Moderation workflow: Roles are Content Moderator and Senior Moderator. At threshold, content enters Pending Review. A moderator other than the proposer approves or rejects with a reason. The proposer may appeal once to a Senior Moderator. Approval alone does not make content playable; the complete curated record must pass validation. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Suggestion eligibility: Wins are lifetime wins on one stable profile. A profile may have one open category suggestion at a time; each contains one category and exactly three pairs. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-CURATEDPAIRS-004 Predefined content

Every playable word pair has a predefined category, prompt word, target answer, and three ordered hints. The game uses this content at runtime and does not generate prompts or hints dynamically. Scope: layer-1+ Acceptance Criteria:

  • Given a category is enabled, when the content catalog is checked, then every playable pair has a prompt, target answer, three ordered hints, accepted-answer policy, and category background. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Content volume and reuse: Seed 10 complete pairs per category. Do not repeat a pair within a match until every playable pair in the selected category has been used; then reshuffle that category’s eligible pool. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-FINALSTANDINGS-014 Game end and tiebreak

A match contains eight rounds. When the eighth round ends, the game displays final standings. If the highest score is tied, sum each player’s accepted solve durations and add 80 seconds for each unsolved round; the lowest total ranks first. Players still tied share the same rank. Scope: layer-1+ Acceptance Criteria:

  • Given the configured final round ends, when players have different total scores, then final standings rank them by total score. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given two or more players tie for the highest score, when final standings are calculated, then total answer time is the sum of each round’s accepted solve duration, with 80 seconds charged for an unsolved round; the lower total ranks first, and an exact tie shares the same rank. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Match length and stage timer: Eight rounds. Each of the four answer stages has a 20-second deadline; a stage closes early when all unsolved players submit. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Total answer time and final tie: Sum server-measured solve durations; an unsolved round adds the 80-second maximum round duration. Sort tied scores by this total; exact remaining ties share rank. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-GUESTADMIN-031 Guest game and owner-only admin PWA release

Provide public guest-only multiplayer and a separate owner-only GitHub administration PWA with isolated authentication and private offline behavior. Scope: layer-1+ Acceptance Criteria:

  • Public gameplay offers guest entry only. Public OpenAI routes return404, member-shaped public sessions fail closed, and public session permissions are empty regardless of stored grants or bootstrap configuration. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Public community submissions, nominations, secured content links and user-delegation controls are disabled; reactions and guest multiplayer remain available. /content redirects to /play and /api/admin/users returns404 without storage. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Only GitHub numeric account16146732 with login sharpninja can establish an admin identity. Authorization Code uses exact registered callbacks, S256PKCE, cryptographic state and a one-time expiring browser-bound challenge; wrong users, duplicate parameters, replay and provider failures issue no admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Guest/admin cookies and stored challenge/session digests use separate namespaces. Copying a token into the other cookie cannot authenticate. GitHub login/logout preserves the guest session. Admin API responses are private/no-store and logout requires same origin and revokes the stored admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Only admin pages link the admin manifest with identity/start/scope /admin and standalone presentation. Only authenticated admin UI offers installation. Public / and /play expose no manifest or install controls. No GitHub secret or provider token is returned to a browser response or client bundle. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • The former root worker retires itself and only wordpairs-pwa-* caches. Admin/unrelated registrations and caches survive. Existing OS-installed icons cannot be removed by website code and are not claimed removed. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Admin worker handles only same-origin GET admin document navigations, preserves HTTP errors and provides a generic offline screen only for network failure. It caches only public offline HTML/icons; no private HTML, sessions, APIs, credentials, RSC, mutations or background actions. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Navigation/private content require the verified admin session; ordinary guest grants cannot authorize admin pages. Callback failure gives safe visible feedback; logout/failure removes links, stale requests cannot restore private state, and active content edits retain audit/version/retry rules. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • The welcome/profile card has responsive internal whitespace of at least20CSSpixels on phone and32CSSpixels on desktop, with readable controls and no horizontal overflow. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • All account/member profiles, including the GitHub administrator and retired OpenAI profiles, are excluded server-side from public leaderboard/landing projections even when their saved public flag is true. New member profiles default private. Guest opt-in, ordering, bounded paging and revision conflict behavior remain unchanged. (evidence: User confirmed 28 October 2026: Hide all old member profiles. Recovery slice tests and migration pending; no completion claimed.)

FR-IDENTITY-018 Identity modes

Players enter public multiplayer as browser-persistent guests with generated aliases, icon preferences and leaderboard opt-out. Public account/OpenAI sign-in is disabled. Separate GitHub administration is restricted to sharpninja and cannot confer privileges on a guest session. Scope: layer-1+ Acceptance Criteria:

  • Given a player chooses guest mode, when they continue without OIDC authentication, then the game creates a guest player identity, allows icon selection, and lets them create or join a room by code. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Guests use generated aliases and can opt into or out of public leaderboard display. All account/member profiles remain excluded under FR-GUESTADMIN-031#GA-10 regardless of their historical public flag. (evidence: Changed guest-only release; retained historical criterion evidence in prior notes. Current GA-10 recovery validation pending.)
  • Public gameplay offers guest entry only. Public OpenAI routes return404, member-shaped public sessions fail closed, and public session permissions are empty regardless of stored grants or bootstrap configuration. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Only GitHub numeric account16146732 with login sharpninja can establish an admin identity. Authorization Code uses exact registered callbacks, S256PKCE, cryptographic state and a one-time expiring browser-bound challenge; wrong users, duplicate parameters, replay and provider failures issue no admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Guest/admin cookies and stored challenge/session digests use separate namespaces. Copying a token into the other cookie cannot authenticate. GitHub login/logout preserves the guest session. Admin API responses are private/no-store and logout requires same origin and revokes the stored admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • All account/member profiles, including the GitHub administrator and retired OpenAI profiles, are excluded server-side from public leaderboard/landing projections even when their saved public flag is true. New member profiles default private. Guest opt-in, ordering, bounded paging and revision conflict behavior remain unchanged. (evidence: User confirmed 28 October 2026: Hide all old member profiles. Recovery slice tests and migration pending; no completion claimed.)

FR-LIVESCORECARD-011 Live round scorecard

The active round page shows every player and updates a player’s row when they solve. A solved row shows the player icon, points earned in the current round, and elapsed answer time. Incorrect guess text is not exposed to other players. Scope: layer-1+ Acceptance Criteria:

  • Given a player solves, when the game accepts the answer, then the active scorecard shows that player’s icon, points earned, and elapsed time from round start. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-LOBBYDISCOVERY-019 Lobby discovery and matching

Players can browse discoverable open lobbies or use quick match to join an available lobby for this game. Any player in a room can share its room code or deep link to invite others; a deep link bypasses lobby browsing. Joining as a player requires an available slot under the configured capacity. An invited player who reaches a full room can choose observer mode. Before the game start countdown begins, if a player leaves a full room, the oldest observer is promoted into the final player position and the remaining players move up to close the vacancy. If player/observer membership or readiness has not changed for 30 seconds before countdown, all players are prompted once for that room version to mark ready; any such change resets the interval. When all but one player have marked ready, the holdout receives a five-second warning. If they mark ready before it expires, the countdown begins immediately. If the warning expires while the holdout remains unready in a room with more than two players, they are removed and the countdown begins. In an exactly two-player lobby, the holdout remains and the lobby waits without starting the countdown until both players are ready. The roster locks when the countdown begins, and a player who leaves afterward is not replaced. Rooms are private by default; the creator may enable discoverability. Quick match selects the oldest eligible room, breaking equal creation times by stable room ID. These joins use the same atomic capacity check. Scope: layer-1+ Acceptance Criteria:

  • Given a discoverable lobby is waiting and has fewer players than its configured capacity, when a player browses lobbies, then the lobby appears as joinable; hidden, full, or started lobbies do not appear as joinable. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given at least one discoverable lobby has an available player slot, when a player uses quick match, then the service assigns them to the oldest eligible lobby, breaking equal creation times by stable room ID, without exceeding configured capacity. Given none is available, then the player is offered browsing or room creation. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player is in a room, when they share its room code or deep link, then another player can use either invitation to reach the intended room, subject to an open slot. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a valid room deep link, when a player opens it, then the game bypasses lobby browsing and checks the room directly. If a slot is open, the player joins; if the room has no open player slot, the invitee is offered observer mode. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a room is full and the start countdown has not begun, when a player leaves and at least one observer is waiting, then the observer who entered observer mode earliest becomes the final player in the roster, existing players move up to close the vacancy while preserving their relative order, and any remaining observers stay observers. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given every player in the room has marked ready, when the final ready state is received, then the game start countdown begins immediately and the player roster locks. If a player leaves after the countdown begins, then no observer or other player is promoted or substituted into that player’s place. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given all but one player have marked ready, when the room enters the holdout state, then the unready player sees a five-second warning to mark ready. If they mark ready before it expires, the start countdown begins immediately. If the warning expires while they remain unready in a room with more than two players, they are removed and the start countdown begins with the remaining roster locked. If the room has exactly two players, the holdout is not removed and the lobby continues waiting; the countdown does not begin until both are ready. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given the game start countdown has not begun, when no player/observer membership or ready-state change occurs for 30 seconds, then the game prompts all players once for that room version to mark themselves ready; any membership or ready-state change resets the interval. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Lobby discovery and quick match: Rooms are undiscoverable by default; creator may opt in. Quick match selects the oldest discoverable waiting room with a free seat, then stable room ID. The same atomic capacity-checked join command handles the selection. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player in an active room, when they choose Copy Invitation Link and link creation and clipboard access succeed, the generated inviter-bound link is copied and displayed, with an accessible confirmation.
  • Given a generated invitation link, when clipboard access is unavailable or denied, the link remains visible for manual copying and an accessible message explains that automatic copying failed.
  • Given a player requests an invitation, if creation fails or returns no link, no new link is shown or copied, no copy-success confirmation is announced, and an accessible error is shown.

FR-LOBBYLIFECYCLE-003 Lobby

The room view displays the room code, configured maximum capacity, available player slots, current players, and observers separately. A game cannot start with fewer than 2 players. Any player in the room can invite others by sharing its code or deep link. The room identifies the anchor player. Before the game start countdown begins, when a player leaves a full room, the oldest observer is promoted into the final player position and the remaining players move up to close the vacancy. If the lobby aggregate has no membership or readiness change for 30 seconds before the start countdown, all players are prompted once for that unchanged state version to mark themselves ready; any membership or readiness change resets the idle interval. When all but one player have marked ready, the holdout receives a five-second warning to mark ready. If they mark ready before it expires, the countdown begins immediately. If the warning expires in a lobby of more than two players and the holdout remains unready, they are removed and the countdown begins with the remaining roster locked. In an exactly two-player lobby, the holdout remains and the lobby waits without starting the countdown until both players are ready. The roster locks when the countdown begins; a player who leaves afterward is not replaced. The room creator has no special readiness or start authority; every player has the same lobby controls. Rooms are private by default; the creator may enable discovery. Quick match selects the oldest eligible room with a free seat and uses stable room ID to break ties. A 10-second countdown begins when all active players are ready. If a player leaves after countdown begins, nobody replaces them. If fewer than two original players remain when the countdown expires, the game is aborted and the room closes. Scope: layer-1+ Acceptance Criteria:

  • Given a valid room code and 2-10 players on separate devices, when each joins, then every joined player appears in the same room with their selected icon. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a room has reached its configured player capacity from 2 through 10, when another player attempts to join, then the game rejects the additional player without changing existing room state. This is true for any configured capacity, including 2, 8, and 10. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a player creates a room, when they set a maximum capacity from 2 through 10, then the room stores and displays that limit. Values below 2 or above 10 are rejected. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a player is in a room, when they share its room code or deep link, then another player can use either invitation to reach the intended room, subject to an open slot. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a valid room deep link, when a player opens it, then the game bypasses lobby browsing and checks the room directly. If a slot is open, the player joins; if the room has no open player slot, the invitee is offered observer mode. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an invited player follows an invitation to a full room, when they choose to observe, then they enter without consuming a player slot and are bound to the player who invited them. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a room is full and the start countdown has not begun, when a player leaves and at least one observer is waiting, then the observer who entered observer mode earliest becomes the final player in the roster, existing players move up to close the vacancy while preserving their relative order, and any remaining observers stay observers. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given every player in the room has marked ready, when the final ready state is received, then the game start countdown begins immediately and the player roster locks. If a player leaves after the countdown begins, then no observer or other player is promoted or substituted into that player’s place. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given all but one player have marked ready, when the room enters the holdout state, then the unready player sees a five-second warning to mark ready. If they mark ready before it expires, the start countdown begins immediately. If the warning expires while they remain unready in a room with more than two players, they are removed and the start countdown begins with the remaining roster locked. If the room has exactly two players, the holdout is not removed and the lobby continues waiting; the countdown does not begin until both are ready. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given the game start countdown has not begun, when no player/observer membership or ready-state change occurs for 30 seconds, then the game prompts all players once for that room version to mark themselves ready; any membership or ready-state change resets the interval. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Host authority and countdown: Creator has no special start/readiness privilege. All players have equivalent controls. All-ready starts a 10-second countdown and locks the roster. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Countdown departure below minimum: Never replace a player who leaves after lock. If fewer than two original players remain at countdown expiry, abort start and close the room. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Idle prompt and readiness races: The 30-second interval resets on player/observer membership or ready-state changes. Prompt once per unchanged aggregate version. Deadlines carry the expected version; stale jobs are no-ops. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-NAVIGATIONDRAFTS-029 Predictable navigation and private drafts

Make Back, Cancel, draft retention and explicit room departure predictable without implicit mutations. Scope: layer-1+ Acceptance Criteria:

  • Back returns to the previous authorized screen and dialog dismissal restores focus to its opener without submitting, changing readiness or leaving room membership. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Unsaved content/editor drafts survive ordinary same-profile in-tab navigation in memory only; reload, tab close, session/profile change and permission loss clear them. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Explicit Cancel/Discard abandons the draft and returns to the originating screen without changing the saved record; a canceled profile edit restores saved alias/icon. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Leaving a room requires an explicit Leave action and applies the existing authoritative membership/departure rules. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Returning to a privileged screen revalidates authorization; back navigation cannot restore cleared private data or another profile's draft. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)

FR-NOMINATIONS-016 Future answer nominations

A player who correctly solves during the no-hint stage may nominate a word as a candidate answer for future games. The game records nominations by candidate word. A candidate becomes eligible for the future answer pool only after it has at least 10 nominations and passes moderation. Before it is playable, content editors must create a complete curated pair record with the required prompt, category, hints, accepted answers, and category background. Nominations do not affect current-round scoring or progression. Scope: layer-1+ Acceptance Criteria:

  • Given a player solves during the no-hint stage, when the round results are shown, then that player can submit a candidate answer nomination; players who solve after any hint cannot nominate from that round. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a candidate has fewer than 10 nominations or has not passed moderation, when the answer pool is checked, then it is not eligible for future games. Given it has at least 10 nominations and passes moderation, then it enters the future-eligible answer pool but is not playable until its full curated pair record exists. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an actor is not authorized to perform moderation or catalog activation, when they attempt to change moderation status or publish content, then the service rejects the action; every change to nomination count, moderation status, and eligibility is auditable; and content cannot be selected for play until it has passed moderation and is complete in the curated catalog. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Nomination counting: Count unique player profiles per normalized candidate. A profile may nominate a candidate once and submit at most one candidate per eligible no-hint solve. Moderation begins at 10 unique nominations. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Moderation workflow: Roles are Content Moderator and Senior Moderator. At threshold, content enters Pending Review. A moderator other than the proposer approves or rejects with a reason. The proposer may appeal once to a Senior Moderator. Approval alone does not make content playable; the complete curated record must pass validation. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-OBSERVERS-020 Full-room observer mode

If an invited player attempts to join a room with no open player slot, the invitee may choose to observe the game in real time from the perspective of the player who invited them. Observer mode does not take a player slot, submit answers, make category choices, or earn points. The observer sees only the game information available to the followed player. Before the game start countdown begins, if a player leaves a full room, the observer who entered earliest is promoted to the final player position; the other players move up to close the vacancy. Remaining observers keep their observer status. When all but one player have marked ready, the holdout receives a five-second warning to mark ready. If all players mark ready before expiry, the countdown begins immediately. If the warning expires in a room with more than two players while the holdout remains unready, they are removed and the countdown begins. In an exactly two-player lobby, the holdout remains and the lobby waits without starting the countdown until both players are ready. The roster locks when the countdown begins. If a player leaves after countdown begins, no observer or other player replaces them; if fewer than two original players remain at expiry, the game start is aborted and the room closes. Scope: layer-1+ Acceptance Criteria:

  • Given an invited player follows an invitation to a full room, when they choose to observe, then they enter without consuming a player slot and are bound to the player who invited them. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an observer is following an inviter, when the inviter’s player-authorized view changes, then the observer sees the corresponding update in real time. Observer attempts to submit guesses, choose categories, or change game state are rejected; the observer earns no score. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a room is full and the start countdown has not begun, when a player leaves and at least one observer is waiting, then the observer who entered observer mode earliest becomes the final player in the roster, existing players move up to close the vacancy while preserving their relative order, and any remaining observers stay observers. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Observer behavior: Maximum 25 observers per room. Signed links bind the inviter. Manual room-code observers select an active player as inviter. Observer arrival order is assigned transactionally; ties use stable profile ID. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-PHONEINSTALL-026 Admin phone home-screen installation

Make the owner-only admin pages installable on supported phones with standalone launch at /admin; disable public-game installation and safely retire its legacy worker/cache. Scope: layer-1+ Acceptance Criteria:

  • Only admin HTML links a manifest with stable id /admin, start_url /admin, scope /admin, Wordpairs Admin names, standalone display, same-origin192/512 PNGs and separate maskable icon. Public HTML has no manifest. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Referenced PNGs have declared dimensions, opaque brand background and safe-zone maskable artwork; Apple metadata and touch icon exist. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Authenticated admin UI registers /admin-sw.js with scope /admin and updateViaCache none in secure contexts, handles failures and uses fresh worker script headers. Public game has no installation UI or active worker registration. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • A captured install event prompts once after a click; dismissal/rejection preserves guidance; installed/standalone state hides install UI and listeners clean up. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Admin-only phone guidance covers iPhone Share/Android menus and HTTPS, with accessible narrow-screen layout. No physical installation or standalone GitHub cookie-context continuity is claimed by emulation. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Network-failed admin document navigation returns a generic static reconnect/sign-in screen without private content; online recovery requires a current admin session. Public/API/auth navigations receive no admin offline fallback. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • API/auth/realtime/private navigation and mutations are never stored in worker caches; offline API/mutation requests fail without receiving offline HTML. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Admin updates remove only older wordpairs-admin-pwa-* caches and wait for active clients. Legacy public retirement removes only wordpairs-pwa-* and root /sw.js registration, preserving admin and other apps. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • The former root worker retires itself and only wordpairs-pwa-* caches. Admin/unrelated registrations and caches survive. Existing OS-installed icons cannot be removed by website code and are not claimed removed. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)

FR-PLAYERPROFILE-002 Player profile

Players enter public multiplayer as browser-persistent guests with generated aliases, icon preferences and leaderboard opt-out. Public account/OpenAI sign-in is disabled. Separate GitHub administration is restricted to sharpninja and cannot confer privileges on a guest session. Scope: layer-1+ Acceptance Criteria:

  • Given a player selects an icon, when that player enters the lobby and plays a round, then the same icon appears in the lobby, live scorecard, and standings. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player chooses guest mode, when they continue without OIDC authentication, then the game creates a guest player identity, allows icon selection, and lets them create or join a room by code. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Guests use generated aliases and can opt into or out of public leaderboard display. All account/member profiles remain excluded under FR-GUESTADMIN-031#GA-10 regardless of their historical public flag. (evidence: Changed guest-only release; retained historical criterion evidence in prior notes. Current GA-10 recovery validation pending.)
  • Public gameplay offers guest entry only. Public OpenAI routes return404, member-shaped public sessions fail closed, and public session permissions are empty regardless of stored grants or bootstrap configuration. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Only GitHub numeric account16146732 with login sharpninja can establish an admin identity. Authorization Code uses exact registered callbacks, S256PKCE, cryptographic state and a one-time expiring browser-bound challenge; wrong users, duplicate parameters, replay and provider failures issue no admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • Guest/admin cookies and stored challenge/session digests use separate namespaces. Copying a token into the other cookie cannot authenticate. GitHub login/logout preserves the guest session. Admin API responses are private/no-store and logout requires same origin and revokes the stored admin session. (evidence: Unverified changed-release obligation; prior evidence retained in notes. Specification: docs/admin_pwa_release.md.)
  • All account/member profiles, including the GitHub administrator and retired OpenAI profiles, are excluded server-side from public leaderboard/landing projections even when their saved public flag is true. New member profiles default private. Guest opt-in, ordering, bounded paging and revision conflict behavior remain unchanged. (evidence: User confirmed 28 October 2026: Hide all old member profiles. Recovery slice tests and migration pending; no completion claimed.)

FR-PUBLICLANDING-022 Public landing page

An unauthenticated visitor can open the game landing page and view an approved leaderboard, public lobby information, and the gameplay rules. Lobby information is limited to approved public metadata for discoverable, joinable rooms and cannot expose private room codes, hidden answers, guesses, observer-only views, or credentials. The leaderboard uses lifetime wins, total points, earliest win, then stable profile ID; it displays generated aliases and honors opt-out. Public lobby cards expose exact player count/capacity, waiting state, and room age, with a 15-second refresh. The rules summary is concise and versioned. Scope: layer-1+ Acceptance Criteria:

  • Given an unauthenticated visitor opens the game landing page, when the page loads, then it presents an all-time leaderboard ranked by lifetime wins, total points, earliest win, and stable profile ID; discoverable lobby cards with exact player count/capacity, waiting state, and room age; and a concise versioned rules summary. It uses generated public aliases, honors opt-outs, excludes room codes and private room/game data, and provides usable empty/unavailable states. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an unauthenticated visitor requests landing-page data, when the service responds, then it returns only lifetime-win ranking fields, opted-in generated aliases, exact counts/capacity, waiting state, room age, and rules version for public content; it does not expose room codes, real identity, guesses, target answers, observer-only views, moderation records, or secrets. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Public landing contract: Leaderboard is lifetime wins, then total points, earliest win, and stable profile ID. Show public alias only, include opt-in guests, honor profile opt-out. Lobby card shows exact player count/capacity, waiting state and room age, never room code/player names. Refresh lobby data every 15 seconds and leaderboard every five minutes. Rules are a concise versioned summary with an updated date. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-REACTIONS-021 Emoji reactions

During any active round, a player can open an emoji menu and display a selected emoji reaction to the room in real time. Reactions do not submit an answer, change round state, or affect scoring. Observers see reactions displayed by the player they are following and reactions broadcast to the room. Scope: layer-1+ Acceptance Criteria:

  • Given a round is active, when a player selects an emoji from the menu, then the reaction appears in real time to room players and observers and does not change scores, answer stage, or round state. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Reactions: Menu: 👍, ❤️, 😂, 🎉, 👀. A reaction is visible for five seconds; one reaction per player per two seconds. It cannot change game state or score. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-REQUESTFEEDBACK-030 Accessible request feedback and safe retry

Specify loading, empty, failed, uncertain-write, conflict and denied UI outcomes without losing safe user input or duplicating commands. Scope: layer-1+ Acceptance Criteria:

  • Pending reads expose accessible loading feedback; empty results are distinct from unavailable results and offer an allowed next action. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Pending mutations show busy feedback and prevent duplicate submission while preserving entered data. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Failures show a user-safe explanation and valid Retry action while retaining safe drafts; raw errors, credentials and private data are not exposed. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • An uncertain-write retry uses identical command identity/payload; conflicts refresh authoritative state without silently replaying a changed command or claiming success. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Authorization denial clears privileged data/drafts and rejects stale response restoration; connection recovery retains accepted state without duplicate writes. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)

FR-RESPONSIVEUI-015 Responsive screens

Profile, lobby, active round, hint stage, scorecard, round summary, category picker, and final standings remain usable on phone-sized screens in portrait and landscape orientation. Scope: layer-1+ Acceptance Criteria:

  • Given a phone-sized viewport in portrait or landscape, when a player enters an answer or views a hint and scorecard, then text and controls remain visible without horizontal overflow. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player uses a phone-sized viewport in portrait or landscape, when they operate primary game actions with touch or keyboard and view text over category backgrounds, then the actions remain operable, text and controls retain clear contrast, focus is visible, and selected icon, solved status, and score changes are indicated by labels or shapes as well as color. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-REVIEWCHANGES-027 Request and resubmit category changes

Provide a reasoned independent change-request workflow for category suggestions without bypassing curation. Scope: layer-1+ Acceptance Criteria:

  • An independent moderator requests changes with a nonempty reason; the suggestion enters changes_requested and remains unplayable. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • changes_requested consumes the proposer's one-open-suggestion slot; another open suggestion is rejected. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Only the proposer can edit and resubmit exactly three pair suggestions; valid resubmission enters pending_review and still requires independent review and complete curation. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)
  • Revision, reason, actor and status transitions are auditable; a change request does not consume the rejection appeal allowance. (evidence: docs/testing/ui/implementation-receipt.json; matching criterion procedure in docs/testing/ui/cases/; exact snapshot docs/testing/ui/implementation-manifest.json. Isolated UI + real application/domain; SQL tier separately identified.)

FR-ROOMACCESS-001 Room access

Players can create a room and set its maximum player capacity from 2 through 10, inclusive. Players can join from their own device by room code, discoverable-lobby browsing, quick match, or a deep link containing the room code. Joining as a player requires an available slot under the room’s configured capacity. If the room is full, an invited player can choose observer mode. Scope: layer-1+ Acceptance Criteria:

  • Given a valid room code and 2-10 players on separate devices, when each joins, then every joined player appears in the same room with their selected icon. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a room has reached its configured player capacity from 2 through 10, when another player attempts to join, then the game rejects the additional player without changing existing room state. This is true for any configured capacity, including 2, 8, and 10. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a player creates a room, when they set a maximum capacity from 2 through 10, then the room stores and displays that limit. Values below 2 or above 10 are rejected. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)
  • Given a player is in a room, when they share its room code or deep link, then another player can use either invitation to reach the intended room, subject to an open slot. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a valid room deep link, when a player opens it, then the game bypasses lobby browsing and checks the room directly. If a slot is open, the player joins; if the room has no open player slot, the invitee is offered observer mode. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • New rooms and new matches allow at most10 players. Existing running matches, including historical larger rosters, remain readable, replayable and finishable; existing members can leave. Oversized legacy waiting/countdown rooms admit no additional player above10 and cannot start a new oversized match. They show a clear10-player compatibility notice and retain Leave. No existing room, player, match or historical result is deleted, and membership/readiness deadlines never forcibly evict members merely to fit the new cap. Legacy rooms with at most10 players may start an allowed match using safe effective capacity. (evidence: Changed current scope31October2026 to cap10; pending fresh exact-source validation and independent Cline Beam High review. Prior full criterion/evidence preserved in docs/evidence/release10/qbrain-before.json.)

FR-ROUNDSUMMARY-013 Round summary

After every round, the game displays a ranking of all players by cumulative score. It also shows the points and elapsed time earned in the completed round. Scope: layer-1+ Acceptance Criteria:

  • Given a round ends, when the summary appears, then it ranks all players by current cumulative score and shows the completed round’s points and solve times. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-SCORELOCK-010 Score lock and elapsed time

When a player submits a correct answer, the game locks that player’s score for the round and records elapsed time from round start to the accepted correct submission. Scope: layer-1+ Acceptance Criteria:

  • Given a player submits the correct answer during the no-hint stage, when the game accepts it, then the player earns 5 points and their round score locks. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player solves after Hint 1, Hint 2, or Hint 3, when the game accepts the answer, then the player earns 3, 2, or 1 point respectively. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player solves, when the game accepts the answer, then the active scorecard shows that player’s icon, points earned, and elapsed time from round start. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given two or more players tie for the highest score, when final standings are calculated, then total answer time is the sum of each round’s accepted solve duration, with 80 seconds charged for an unsolved round; the lower total ranks first, and an exact tie shares the same rank. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given an unsolved player submits an answer for the current stage, when the server checks it against the target and approved accepted-answer list, then only a correct answer is accepted and scored once for that stage; an incorrect answer does not change score or expose guess text to other players; and the target answer remains unavailable to clients until round reveal. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-SCORING-009 Scoring

The game awards 5 points for a correct answer before hints, 3 after Hint 1, 2 after Hint 2, and 1 after Hint 3. A player who remains unsolved after Hint 3 earns 0 points. Scope: layer-1+ Acceptance Criteria:

  • Given a player submits the correct answer during the no-hint stage, when the game accepts it, then the player earns 5 points and their round score locks. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player solves after Hint 1, Hint 2, or Hint 3, when the game accepts the answer, then the player earns 3, 2, or 1 point respectively. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given a player remains unsolved after Hint 3, when the round ends, then the player earns 0 points and the game reveals the correct answer. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)
  • Given two or more players tie for the highest score, when final standings are calculated, then total answer time is the sum of each round’s accepted solve duration, with 80 seconds charged for an unsolved round; the lower total ranks first, and an exact tie shares the same rank. (evidence: docs/phase6_execution.md; docs/admin_acl_review_receipt.json; docs/testing.md)

FR-WORDHINTEDITOR-025 Manage word pairs and ordered hints

Authorized content staff can create and edit complete word pairs and hints while preserving playable catalog integrity and active/historical rounds. Scope: layer-1+ Acceptance Criteria:

  • Each pair has a prompt, canonical target, accepted alternatives, approved category/background and exactly three ordered bounded hints; invalid input is rejected without mutation. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Creation uses an unused ID at version0; edits append exactly the next version without moving an existing ID to another category. Community activation cannot reuse IDs or skip versions. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Current and historical rounds retain their recorded versions. Future selection chooses the highest version per ID and then selectability, with no fallback to older excluded versions. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)
  • Reasoned exclusion preserves history and cannot empty an initial category. Both editorial and community publication reject a projected total above4096 historical approved versions atomically. (evidence: docs/admin_acl_design.md; docs/admin_acl_review_receipt.json; tests/browser/admin.phase6.spec.ts; tests/integration/admin.integration.ts)

Clone this wiki locally