Skip to content

Award feats immediately and add dice, streak, rating, and pace feats - #122

Merged
ejsmith merged 7 commits into
mainfrom
instant-feats
Oct 29, 2026
Merged

ejsmith merged 7 commits into
mainfrom
instant-feats

Conversation

@ejsmith

@ejsmith ejsmith commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Feats like One-Inch Punch were only written when a game completed, so a player who landed a 1 saw nothing on their profile until the end. Feats that can't depend on the outcome are now awarded on the move that earns them.

  • Instant feats (instantFeatKeys in shared/feats.ts): One-Inch Punch, Revenge Is Sweet, Counter Culture, Natural, Bought It, Worth Every Penny, Whiff City, Can't Miss Missed, Boomerang, Sucker Born Every Minute, All In All Gone, Bought a Goose Egg, Buyer's Remorse, Scratch Fever. Each only ever turns on as more actions are replayed, so a later move can't take one back.
  • When it runs: after a punch, a score or scratch, a Mulligan, or a five-of-a-kind roll, game-action replays the game's actions and adds new player_feats rows to the same atomic move. Other moves skip the read.
  • Turn-level feats (Sucker chases, Bought a Goose Egg) settle when the turn is submitted or Mulliganed, never mid-turn.
  • Completion still computes every feat and skips the ones already stored for that game (the table's primary key is game, player, feat). Games already in progress pick up missed instant feats on their next qualifying move or at completion.
  • Rage Quit: instant feats earned before the quit stand; the quit still awards nothing more.
  • AGENTS.md: try-out URLs use the Tailscale MagicDNS name, and the local Supabase URL must be reachable over the tailnet.

New dice feats

All of these are instant feats.

  • Sucker count tiers: Seeing Double (2 Suckers in one game), Hat Trick (3), Four Score (4), High Five (5), Six Pack (6), Lucky Seven (7), Crazy Eights (8). Each tier is its own feat, so a 6-Sucker game earns all five tiers up to Six Pack. A Sucker counts once per run of rolls, and a run starts over at each submission and Mulligan, so rolling again with all five held doesn't count twice. A punched turn's replay counts again.
  • Back-to-Back: a Sucker on two of your turns in a row.
  • Anything You Can Do: a Sucker on the turn right after your opponent rolled one.
  • Lightning Strikes Twice: a Sucker you scored is punched away, and you roll another on the replay.
  • Straight Out the Gate: a large straight on the first roll of a turn.
  • Four on the Floor: exactly four of a kind on the first roll of a turn.
  • Daylight Robbery: land a punch on a turn that scored a Sucker. Its mirror is the new Sucker Punched defeat.
  • mayEarnInstantFeat now takes the roll number, so the first-roll feats only replay the game on a turn's first roll.
  • Migration 20261028140000_backfill_sucker_count_feats.sql credits finished games with their Sucker count tiers, using the same counting in SQL. Games still in progress pick theirs up on their next qualifying move. The other new feats apply going forward.
  • Merged main, which fixes the expo-notifications version check that failed the App job. deno.lock picks up the same bump.

Streak, rating, pace, replay, and scorecard feats

  • Instant: Flurry (land 3+ punches in a game); Thanks, I Needed That and Backfire (a punched replay outscores the erased turn, for the victim and the puncher); Do-Over and Should've Kept It (Mulligan a submitted turn and score 20+ more, or less, on the redo). A replay is judged against the erased turn's score, a scratch counting as 0.
  • At completion (TypeScript): Rope-a-Dope (win after dodging 3+ punches), Wire to Wire (lead after every turn from your first, and win), Dead Heat (tie), 400 Club and 500 Club, Spotless (no zeros or scratches), and the Double Digits defeat (under 100). The clubs start at 400 because the computer player averages 292 and reaches 300 in 42% of 5,000 simulated games, 400 in 8%, and 500 in 0.5%.
  • Completion trigger, per game: Giant Killer and Banana Peel (beat someone rated 200+ above you, or lose to someone 200+ below, using shown ratings before the game and only once both players have 5+ earlier games), Blitz (finish within an hour of the first roll), and The Long Game (30+ days). A Rage Quit awards none of these.
  • Completion trigger, milestones: Hot Streak and On Fire (win 5 / 10 in a row), Got Your Number (beat the same opponent 5 in a row), and Sucker Collector (a Sucker of every face across finished games). The new player_standings.sucker_faces column keeps the faces as bits 1 to 6, so the check never rescans history.
  • Migration 20261029120000_add_streak_rating_and_pace_feats.sql adds that column, replaces apply_completed_game_standings, and backfills the faces and the new milestones on the game that earned each one. Per-game feats start with games finished after it.
  • The same migration adds a turn_actions (game_id, created_at) index. Instant feats load a game's actions on most moves, and turn_actions had no index on game_id.

Needs supabase db push and a game-action deploy.

Testing

  • npm run typecheck, npm run typecheck:edge, npm run test, npm run test:edge
  • npm run test:integration:supabase (47 passed). The completion test now checks that Natural and Scratch Fever are stored mid-game and that completion doesn't insert them twice.
  • Tried by hand against local Supabase: a landed punch on a 1 showed One-Inch Punch on the profile mid-game.
  • New Deno tests cover the tier counting (held rerolls, Mulligans, the opponent's Suckers), turn-before checks across punched turns, robbery and replays, and first-roll patterns. The integration test's scratch-only game now expects the tiers it earns mid-game. That suite runs in CI; it wasn't run locally after the new feats because it resets the shared local Supabase stack.
  • Ran the backfill SQL against synthetic rows in a rolled-back transaction on local Postgres: held rerolls, Mulligans, bad dice payloads, unfinished games, and existing feat rows.
  • Applied the new migration on top of local data in a rolled-back transaction and completed synthetic games through the trigger. An underdog win earned Giant Killer, Banana Peel, Blitz, Hot Streak, Got Your Number, and Sucker Collector. A rage quit earned no per-game feats, a 2-hour game no Blitz, and a 31-day game The Long Game.
  • The integration completion test now expects Dead Heat, Double Digits, and Blitz for its 0-0 scratch game, and no rating feats for first-time players.

🤖 Generated with Claude Code

ejsmith and others added 2 commits October 23, 2026 15:47
Feats like One-Inch Punch, Natural, and Scratch Fever cannot depend on how
the game ends, but were only written at completion. game-action now replays
the game's actions after any punch, submission, Mulligan, or five-of-a-kind
roll and stores newly earned instant feats in the same move. Completion
still computes every feat and skips the ones already stored for the game.
Instant feats earned before a Rage Quit stand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T15:40:05.204543Z 8c8bb4e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

ejsmith and others added 2 commits October 28, 2026 22:18
# Conflicts:
#	supabase/functions/game-action/index.ts
Rolling 2 through 8 Suckers in one game now earns Seeing Double, Hat Trick,
Four Score, High Five, Six Pack, Lucky Seven, and Crazy Eights. A Sucker
counts once per run of rolls, which starts over at each submission and
Mulligan, so rolling again with all five held does not count twice.

Also adds Back-to-Back, Anything You Can Do, Lightning Strikes Twice,
Straight Out the Gate, Four on the Floor, and Daylight Robbery, plus the
Sucker Punched defeat. All are instant feats. A migration credits finished
games with their Sucker count tiers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ejsmith ejsmith changed the title Award outcome-independent feats immediately Award feats immediately and add Sucker count feats Oct 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0a1517ce2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_shared/feats.ts Resolved
ejsmith and others added 2 commits October 28, 2026 22:32
A Mulligan in the response window discards the turn just submitted, so its
replay is that turn again, not the next one. Back-to-Back and Anything You
Can Do no longer count the discarded turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
game-action now awards Flurry, Thanks I Needed That, Backfire, Do-Over, and
Should've Kept It the moment they happen, and Rope-a-Dope, Wire to Wire,
Dead Heat, the 400 and 500 Clubs, Spotless, and Double Digits at completion.
A replay is judged against the score of the turn it erased.

The completion trigger awards Giant Killer, Banana Peel, Blitz, and The Long
Game per game, and the Hot Streak, On Fire, Got Your Number, and Sucker
Collector milestones. player_standings.sucker_faces tracks the Sucker faces
each player has rolled, and the migration backfills those bits and the new
milestones. Instant feats replay a game's actions on most moves, so
turn_actions gets a (game_id, created_at) index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ejsmith ejsmith changed the title Award feats immediately and add Sucker count feats Award feats immediately and add dice, streak, rating, and pace feats Oct 29, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52a10a846a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_shared/feats.ts Resolved
A Mulligan discards every roll so far, so a Sucker rolled before it no
longer marks the turn for Back-to-Back or Anything You Can Do. The Sucker
count still includes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ejsmith
ejsmith merged commit d335850 into main Oct 29, 2026
12 checks passed
@ejsmith
ejsmith deleted the instant-feats branch October 29, 2026 20:14
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