User-driven rebalance. Benjamin researched the rarity math: in a 6-card hand a 3-of-a-kind is ~21-25× rarer than a pair, but v1.0 scoring (+1/card flat) only paid 1.5× for it. That meant holding a pair to grow into a 3-set was strictly bad math — lay the pair now for +2 and 2 replacement cards, or hold for a tiny chance at +3. Strategist AI's "hold pairs early" heuristic was technically incorrect against the v1.0 scoring.
New scoring: triangular (sum 1+2+...+N). Each additional card in a match is worth more than the last:
2-card match: +3 (was +2)
3-card match: +6 (was +3)
4-card match: +10 (was +4)
5-card run: +15 (was +5)
6-card run: +21 (was +6)
Penalty unchanged: −1 per card left in hand at round end.
Why triangular and not rarity-literal: literal scoring would mean a single lucky 3-set decides every round (~21× more points than a pair would dominate). Triangular tracks rarity directionally — 2× reward for a 3-set, 3.3× for a 4-set — without making any one match game-winning. Typical winning scores now ~30-60 (was ~15-30).
Implementation: new matchScore(size) helper in data/cards.js. attemptLayDown (player) + AI build loop both use it. Played-match tags, lay-down button label, and log lines all show the actual points (+6, not +3).
Per-game version 1.1.1 → 1.2.
v1.1.1 — hot-seat perspective bug fix
Reported by user: "The top screen in hot seat always shows player 2. So when it is player 2's turn on hot seat they can't see any of player 1's matches." Cause: meSide() was correctly returning state.current in hot-seat mode (so the active player's hand appeared at the bottom), but oppSide() was still hardcoded to 'p2'. When P2 was active, both meSide() and oppSide() returned 'p2', so the top zone (opponent) and bottom zone (you) both rendered P2's data — P1's played matches and face-down hand were invisible.
Fix: oppSide() now mirrors meSide(), returning whichever side ISN'T active. Two-line change. The vs-AI path is unchanged (meSide always 'p1' → oppSide always 'p2'); only hot-seat behavior is affected.
Per-game version 1.1 → 1.1.1.
v1.1 — hot-seat mode
Two players on one device. New mode tab on the setup screen ("vs AI" / "Hot-seat"). Hot-seat takes both player names and starts a normal game with no AI.
Pass-the-device overlay between turns. Solid-background overlay covers the screen between turns showing the next player's name + their current score + hand size; only dismissed when they tap "I'm ready". Keeps the previous player's hand secret. (For vs-AI mode the overlay is suppressed during AI turns — no human to hand off to.)
Perspective swaps each turn. Active player's hand always appears at the bottom (face-up + clickable); inactive player's hand appears at the top (face-down). Implemented by making meSide() return state.current in hot-seat mode, so the existing render code naturally follows the active player.
Setup screen remembers the last-used mode (and both sets of names independently) so a repeat hot-seat session opens straight onto that tab.
Per-game version 1.0.2 → 1.1.
v1.0.2 — replace cards as you lay them down
Swap v1.0.1's deadlock fix for a more rewarding one. v1.0.1 refilled hand to 6 at the end of every turn. It killed the deadlock but it felt too gin-rummy — the "race to empty your hand" tension of v4 evaporated because you were always back at 6 cards regardless of how well you played.
New mechanic: every time you lay down a match, immediately draw replacement cards from the deck equal to the match size (a 3-card run = 3 replacements). Productive turns refresh you; idle turns don't. Hand size still flexes naturally — if you don't make matches, you're not getting new cards, and the round still terminates at the natural pace.
Partial replacement if the deck is short. Emptying the deck via replacements (rather than start-of-turn draws) triggers the same deckEmptied final-round counter.
AI's build loop re-evaluates after each lay-down, so it gets to USE its replacement cards in the same turn (chained lay-downs become possible when you replace into another matching card).
Rules page updated to reflect the new mechanic.
v1.0.1 — end-of-turn refill (fixes deadlock)
Bug fix reported by Benjamin: "I had one card and I drew but I couldn't play it because then I would have 0 cards. That's okay but that means I discard it then I draw but I still don't have enough cards for leftover after I would make a pair so I was stuck in an endless loop." Exactly right — the v4 rules don't account for the case where hand shrinks to 1-2 cards with no playable match. Cards just cycle 1 ↔ 2 forever.
Rule change per Benjamin's request: at the end of each turn (after discard), the active player refills their hand from the deck back to HAND_SIZE (6 cards). Closer to gin/rummy feel. Partial refill if the deck doesn't have enough — that's what triggers the final-round counter.
Same final-round handling: when the deck empties (either via someone's start-of-turn draw OR mid-turn refill), every other player gets one more turn, then scoring runs.
Rules page updated with the new refill step.
v1.0 — first playable
Initial scaffold from Benjamin's v4 spec (uplinked 2026-06-26 over four iterations: discard-conquer → sprint-snap-score → -v-2 → -v-3 → -v-4). v4 is canonical.
2-player vs heuristic AI. Standard 52-card deck, 6-card deal.
Three AI difficulty tiers selectable on the setup screen:
Apprentice — random draw choice, lays down any match it sees, random discard. Beatable by a kid learning the rules.
Standard — takes the discard pile card only when it completes or extends a match in hand; lays down every available match (largest first); discards the highest-rank card not part of a partial match.
Strategist — same as Standard but holds size-2 sets ("pairs") early in the round hoping to grow them into 3-of-a-kind (worth 3 points instead of 2); biases discards away from ranks the opponent has already played as sets (avoids feeding their 4th card).
Turn loop: DRAW → BUILD → DISCARD. Build step enforces the v4 "must keep ≥1 card" rule plus the explicit 2-card edge case (can't lay down a pair if it would empty your hand). Discard step auto-skips when hand has exactly 1 card.
Round end: when the draw pile empties, each player gets one more turn (tracked via finalTurnsLeft), then scoring runs (+1 per card in a match, −1 per card left in hand).
Persistent setup screen: player name, AI name, AI difficulty saved to localStorage.
Pi-friendly card visuals: white parchment-colored cards with red/black suit glyphs, no images. Mobile-responsive (single-column phone layout, smaller cards at max-width: 500px).