diff --git a/docs/superpowers/specs/2026-03-12-prediction-scoring-design.md b/docs/superpowers/specs/2026-03-12-prediction-scoring-design.md new file mode 100644 index 0000000..b8932cc --- /dev/null +++ b/docs/superpowers/specs/2026-03-12-prediction-scoring-design.md @@ -0,0 +1,87 @@ +# Prediction Scoring — Design Spec + +**Date:** 2026-03-12 +**Status:** Approved + +--- + +## Goal + +Allow the host to enter actual ESC results, calculate prediction scores for all players, and display them on the leaderboard. + +## What Exists + +- Players submit predictions (1st, 2nd, 3rd, last place) during lobby/pre-show via `PredictionsForm` +- Predictions lock when host advances to live-event act +- `GameManager` stores predictions in a `Map` +- Leaderboard already shows jury (J:) and bingo (B:) points +- DB schema has `actual_winner`, `actual_second`, `actual_third`, `actual_last` columns on `rooms` table (nullable, set when host enters results) + +## New Functionality + +### 1. Host Enters Actual Results + +- New component: `ActualResultsForm` — shown in the Host tab during `scoring` and `ended` acts +- Same country-picker UX as the predictions form (select from lineup, all 4 must be different) +- New WS message: `submit_actual_results` (client → server) with `{ winner, second, third, last }` +- Server stores on the `GameManager` (in-memory) and broadcasts updated game state +- Host can re-submit to correct mistakes (overwrites previous entry) + +### 2. Server Scores Predictions + +- `GameManager.setActualResults(winner, second, third, last)` — stores the actual results +- `GameManager.getPredictionScore(playerId)` — compares player's prediction to actuals: + - `first` matches `winner`: 25 pts (`scoring.prediction_winner`) + - `second` matches `second`: 10 pts (`scoring.prediction_top3`) + - `third` matches `third`: 10 pts (`scoring.prediction_top3`) + - `last` matches `last`: 15 pts (`scoring.prediction_nul_points`) + - Total possible: 60 pts +- Prediction scores feed into `buildLeaderboard` as a new `predictionPoints` field + +### 3. Leaderboard Update + +- `LeaderboardEntry` gains `predictionPoints: number` +- Before results are entered: shows `P:?` on the leaderboard +- After results are entered: shows `P:` with actual points +- `totalPoints` includes prediction points (0 if no results entered yet) + +### 4. Player View — Results Reveal + +- After actual results are entered, each player's `gameState` includes `actualResults: { winner, second, third, last } | null` +- `PredictionsForm` (locked state) gains visual indicators: green checkmark for correct predictions, red X for incorrect +- Players who didn't submit predictions get 0 prediction points + +### 5. Display View + +- Shows actual results summary when entered +- Shows leaderboard with prediction scores revealed + +## WS Messages + +**Client → Server:** + +| Type | Payload | Guard | +|---|---|---| +| `submit_actual_results` | `winner`, `second`, `third`, `last` (country codes) | Host only, scoring or ended act | + +**No new server → client messages** — the existing `game_state` broadcast carries all the data. + +## GameState Changes + +```ts +// Added to GameState +actualResults: { winner: string; second: string; third: string; last: string } | null + +// LeaderboardEntry gains +predictionPoints: number +``` + +## Scoring Config Values (existing) + +```json +{ + "prediction_winner": 25, + "prediction_top3": 10, + "prediction_nul_points": 15 +} +```