Skip to content

Build core Scribble gameplay loop: rooms, drawing, scoring, and round results#140

Open
sudevkrishnan wants to merge 32 commits into
everest-engineering:mainfrom
sudevkrishnan:scribble-lab-sdev
Open

Build core Scribble gameplay loop: rooms, drawing, scoring, and round results#140
sudevkrishnan wants to merge 32 commits into
everest-engineering:mainfrom
sudevkrishnan:scribble-lab-sdev

Conversation

@sudevkrishnan

@sudevkrishnan sudevkrishnan commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Implements the full multiplayer drawing-and-guessing game loop, delivered in four spec-driven phases:

  • Room & lobby — Room creation/join with auto-assigned host, validated join codes (400/404 on bad/unknown codes),
    auto-polling lobby (~2s), session-persisted participant identity (reattaches on reload), host-gated start (403
    non-host, 409 under 2 players).
  • Player & word setup — Player name validation, drawer assignment, secret-word visibility scoped to the drawer
    only.
  • Drawing & scoring — Real-time drawing canvas with stroke sync, guess submission, deterministic scoring; guesses
    from unknown participantId are rejected.
  • Round result & restart — Host-only "End Round" action revealing the word and scores, and a "Restart" action to
    begin a new round; verified end-to-end against a live backend.

Each phase followed spec → plan → tasks → implement, with dedicated review/analyze passes closing gaps (e.g.
non-host navigation, round-end as a manual host action, unknown-participant guess rejection).

Contributor

sudevkrishnan and others added 30 commits June 17, 2026 08:41
Covers room creation/host assignment, join validation (empty/invalid
format/not-found), room isolation, ~2s lobby polling, and host-gated
start with a 2-player minimum. Clarified the invalid-format vs not-found
code boundary and required client-side identity persistence across
browser reload, while reaffirming no server-side database/persistence
project-wide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds host/canStart fields to the room snapshot, a POST /:code/start
gating endpoint, sessionStorage-based identity reattachment (tab-scoped
to preserve two-tab testing), and ~2s lobby polling — all additive
within the existing api/services/models and state/services/pages
layering, per the plan's Constitution Check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
35 tasks across setup, foundational (shared model/snapshot fields +
sessionStorage identity helper), and the four user stories (host
creation, join validation, lobby polling, host-gated start), plus a
polish phase for reload reattachment and final quickstart/test validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    - Room creator is auto-assigned host; isHost/canStart exposed on snapshots
    - Join validates empty/invalid-format/not-found codes with distinct
      400/400/404 responses
    - Lobby auto-polls every ~2s, replacing the manual refresh button
    - POST /rooms/:code/start gates on host + 2-player minimum (403/409),
      flipping room status to active (gameplay logic deferred to next phase)
    - Client participant identity persists in sessionStorage so a tab reload
      reattaches to the same room/role; stale identities are cleared and
      surfaced as a clear error on the Start screen

    19 backend tests / 16 frontend tests passing; both packages build clean.
    Verified end-to-end via curl against a live server and manually in two
    browser tabs.

    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lity

Covers trimming/rejecting blank names on create and join, assigning the
host as drawer at round start, deterministic room-code-derived secret
word selection, and drawer-only visibility of both the secret word and
the candidate word list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tightens create/join name validation to trim+reject blank via Zod;
assigns drawer (always the host) and a room-code-derived secret word
once at successful start; redacts secretWord/availableWords from
non-drawer snapshots server-side, while isDrawer stays public per
participant so everyone can see who's drawing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PyYAML is now installed, so the agent-context extension's
update-agent-context.sh can run; minor rewording of the CLAUDE.md
pointer line, same target file as the manual edit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ment

22 tasks across setup, foundational (model fields + word-hash helper),
and four P1 user stories (name validation, drawer assignment, word
selection, drawer-only visibility), plus a polish phase for final
quickstart/test validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/speckit-analyze (run after tasks, before implement) found a CRITICAL
coverage gap: nothing routed non-host participants to the game screen
when the round started, since LobbyPage only navigated the host
explicitly in handleStart(). A guesser would be stuck on the Lobby
screen indefinitely.

Adds FR-012/SC-006/an edge case/an acceptance scenario to spec.md, a
research.md decision documenting the fix (LobbyPage watches
room.status via existing polling, navigates everyone once active) and
why it's verified manually rather than via a new component-test
dependency, a LobbyPage.tsx task (T014) in tasks.md, and updates
plan.md/quickstart.md accordingly.

Also rewords the "Round" Key Entity in spec.md to match data-model.md
(flat fields on Room, not a separate stored object) per a LOW-severity
finding from the same analysis pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements all four user stories from spec 002-drawer-word-assignment:
- createRoomSchema/joinRoomSchema reject blank/whitespace playerName
  with "Player name is required"; removes the old "Player" fallback
- startGame() assigns the host as drawer and a room-code-derived
  secret word (selectSecretWord, deterministic, no RNG) the first time
  a room goes active; repeated starts don't reassign either
- toRoomSnapshot() redacts secretWord/availableWords for non-drawers;
  isDrawer is public per participant so everyone sees who's drawing
- LobbyPage now navigates every participant (not only the host) to
  /game once room.status becomes active, closing a gap /speckit-analyze
  found before implementation; GamePage shows a Players list with
  Drawer/Guesser labels and the secret word when present

35 backend tests / 18 frontend tests passing; both packages build
clean. Verified end-to-end via curl against a live server (blank-name
rejection, drawer/word assignment, drawer-only redaction, determinism
and idempotency across repeated starts/fetches).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds spec for User Stories 1-5 covering the drawer's interactive
canvas/clear action, validated guess submission, polling-synced guess
history, and deterministic 100/0 scoring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds research, data model, API contract, and quickstart for the new
canvas/clear, guess validation, redacted guess-history sync, and
deterministic scoring mechanics. Also folds in the guess-text
visibility clarification from the clarify session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Breaks phase 3 into 43 tasks across Setup, Foundational, and five
user-story phases (US1-US5) per spec.md priorities, plus a polish
phase for end-to-end quickstart validation and the full test suite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/speckit-analyze flagged that toRoomSnapshot() didn't actually emit
strokes until the US4 redaction task, breaking US1/US2's own
quickstart scenarios (guesser-side canvas sync) at their checkpoint.
Moves that wiring into a new Foundational task (T006) since strokes
carry no redaction rule. Also tightens US5's test coverage for
FR-014 (post-correct incorrect guesses) and SC-006 (repeat-trial
determinism).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements phase 3 (003-drawing-canvas-guessing): drawer-only
freehand canvas with clear, validated guess submission (trim,
case-insensitive, drawer rejected), per-viewer redacted guess
history (incorrect guesses hidden from other guessers until
correct), and atomic +100/+0 scoring. Backend gains addStroke,
clearCanvas, submitGuess plus POST /:code/strokes, /:code/clear,
/:code/guesses; toRoomSnapshot now redacts guess text per viewer.
Frontend gains DrawingCanvas (with an isolated, unit-tested
pointer-to-stroke builder), and wires GuessForm/ResultPanel/
Scoreboard to live room state.

Also fixes a polling gap found during manual verification: GamePage
never started polling (only LobbyPage did, and it stopped on
navigating away), so a participant's view only updated on their own
actions. GamePage now polls on mount like LobbyPage, so canvas/guess/
score updates from other participants sync within one cycle.

68 backend tests, 23 frontend tests, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a 24-item requirements-quality checklist covering core gameplay
logic, polling sync, UX clarity, and security/input-integrity for
phase 3. Resolves the three highest-value findings directly in
spec.md:
- Edge Cases: replaced unquantified "continuous-looking line" with a
  verifiable per-pointermove-event rendering rule.
- SC-005: clarified that "one polling cycle" is the pass/fail
  threshold and "~2s" is descriptive context only.
- New FR-020: sets a minimum display bar for redacted guess-history
  entries (guesser identity + incorrect indicator), closing a gap
  between the data contract (FR-018) and UI requirements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…list gaps

Resolves all 24 items in the gameplay-review checklist. Most were
already satisfied by the existing spec; a few real spec gaps are
fixed directly:
- FR-003a + new US1 scenario: a zero-movement pointer-down/up is now
  an explicit, valid single-point stroke (a dot).
- New Edge Case bullet: drawer/guesser role checks are explicitly a
  server-side guarantee, not a UI-only restriction, mirroring phase
  2's non-UI-path guarantee for secret-word redaction.
- New FR-005a: clear and guess controls must be hidden or visibly
  disabled for the role that can't use them.

Also surfaced and fixed a real bug while resolving CHK021:
submitGuess() only checked participantId against the drawer's ID,
never against the room's actual participant list, so a fabricated
participantId could still get a guess recorded and broadcast to
everyone. Adds FR-009a, a not_participant failure reason (403,
"participantId is not a member of this room"), and regression tests
at both the service and route level. contracts/rooms-api.md updated
to document the new error case.

70 backend tests passing (up from 68).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 4 of the gameplay loop: shared unredacted result state (word,
scores, full guess history) visible to all players, plus host-triggered
restart that returns everyone to the lobby with players preserved and
all round state cleared.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves the key ambiguity in the result/restart spec: rounds end only
via an explicit host-triggered "End Round" action, never a timer or
auto-detection, consistent with the project's no-timer constraint and
the host's existing sole authority over game start/restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "result" Room.status entered only via a host-only "End Round"
action, with toRoomSnapshot revealing the secret word and full guess
text to every viewer while in that state; restart returns the room to
"lobby" preserving participants and clearing all round-specific fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two P1 user stories (shared result reveal, host restart to clean
lobby), gated behind a shared RoomStatus widening in the Foundational
phase, with tests at every layer per the constitution's discipline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
F1 (HIGH): ResultPage must poll, or a participant already on /result
never observes the host's restart. F2: GamePage needs a symmetric
lobby-skip safety net to match LobbyPage's result-skip one. F3: add a
restart-then-startGame-again test so FR-012/SC-005 has automated
coverage, not just manual quickstart. F4: clarify in Assumptions that
restart-then-replay is the lab's explicit Scenario 4 deliverable, not
the constitution's prohibited automatic multi-round/drawer-rotation
mechanic. F5: pin down FR-003's score visibility with an explicit
result-state assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds RoomStatus "result", host-only endRound/restartRoom in
roomStore, their routes/schemas, and a toRoomSnapshot bypass that
reveals the secret word and every guess's text to all viewers once
a room is in the result state. Restart resets drawer/word/strokes/
guesses/scores while preserving the participant roster.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GamePage gains a host-only "End Round" button and navigates to the
new ResultPage once the room enters the result state (with a
lobby-skip safety net for a missed transition). ResultPage shows the
revealed word/scores/history and a host-only "Restart" button,
polling so it observes the host's later restart; LobbyPage gains a
matching result-skip safety net for reconnecting clients.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Confirmed via curl: guess text/secretWord redacted during the active
round and fully revealed to every viewer after End Round; End Round
and Restart both reject non-host callers and wrong room states;
Restart resets all round state and preserves the participant roster;
a restarted room starts an independent second round cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sudevkrishnan and others added 2 commits June 17, 2026 15:36
New review.md checklist tests requirements quality across state-
transition correctness, edge cases, UX clarity, and cross-phase
consistency. It surfaced three real spec gaps, all already satisfied
by the implementation (closing documentation only, no code changes):
FR-013 requires host-only End Round/Restart controls to be hidden for
non-host viewers; FR-010 now states the restart reset is atomic; a new
Edge Cases bullet confirms drawing/guessing is rejected once a round
has ended, reusing the existing active-round gate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant