# Prompt for Unity Cursor — hidden MMR winner report Copy everything below the line into Unity Cursor. Do not change join/collect, rematch, or client UI. --- You are working in the Kick Kings **dedicated server** Unity project. The matchmaker API now computes **hidden MMR** on settle. Players must **never** see MMR. Do not add MMR to HUD, post-match screens, profile, or any client-bound packet. Your only job: extend the existing `PATCH /internal/match/{matchId}/winner` JSON body so every settle includes scores and a forfeit flag. Keep the existing header `X-Dedicated-Server-Secret`. Call winner **exactly once** when the outcome is known (retries with the same `winner` are safe). ## Endpoint (unchanged URL) `PATCH /internal/match/{matchId}/winner` ## New required JSON body Always send all six fields. `winner`, `red_connected`, and `blue_connected` already exist — do not remove them. ```json { "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 3, "blue_score": 1, "forfeit": false } ``` | Field | Type | Rules | |-------|------|--------| | `winner` | `"red"` or `"blue"` | Winning side. Unchanged. | | `red_connected` | bool | Red still had a live connection at settle. Do **not** infer from `winner`. | | `blue_connected` | bool | Blue still had a live connection at settle. | | `red_score` | int | Rounds/sets red has won (first-to-3). Send on **every** settle, including forfeits. Use the score at the moment the match ended (0–3 typical). | | `blue_score` | int | Same for blue. | | `forfeit` | bool | `true` for leave, disconnect, last-disconnect-wins, **and skip/auto-forfeit** (two skipped turns). `false` only for a normal score win. | JSON types must be real booleans and integers, not strings (`true` not `"true"`, `3` not `"3"`). ## How the API uses this (do not reimplement) The API applies hidden MMR. You only report facts. | What you send | MMR % the API uses | |---------------|--------------------| | `forfeit: true` **and** loser `*_connected: false`, or both disconnected | 20% | | `forfeit: true` **and** both still connected (skip/auto-forfeit) | score table (100% / 90% / 75%) | | Normal finish, loser score 0 (3–0) | 100% | | Normal finish, loser score 1 (3–1) | 90% | | Normal finish, loser score 2+ (3–2) | 75% | Skip-forfeit: both still connected → `forfeit: true` and send the actual score. API uses the score table, not 20%. The skipper (loser) does **not** get CC. If you omit scores, the API treats the match as 3–0 (100%). That is wrong for 3–1 and 3–2. **Always send scores.** ## Connected flags (unchanged meaning) Set a side to `false` when: - Mirror reports disconnect and reconnect grace expired - That player pressed Leave (even if TCP is still up for a moment) `true` = still in the match when you report the outcome (score win, opponent forfeit, or skip-forfeit while they stayed). CC (100) is awarded only to connected finishers who did **not** forfeit. Last-disconnect winner can be disconnected: they get RC, not CC. Skip/auto-forfeit: skipper gets no CC even if still connected. Do not infer `*_connected` from `winner`. ## What to send in each end condition ### 1. Normal score win (first to 3) Both still in the match. ```json { "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 3, "blue_score": 1, "forfeit": false } ``` Use the real score: 3–0, 3–1, or 3–2. `forfeit` is **false**. ### 2. One player leaves or disconnects Remaining player wins. Disconnected side `false`. `forfeit: true`. Scores = rounds already won when they left. ```json { "winner": "red", "red_connected": true, "blue_connected": false, "red_score": 1, "blue_score": 0, "forfeit": true } ``` ### 3. Both disconnect (last-disconnect-wins) Last player who disconnected is `winner`. Both `*_connected: false`. `forfeit: true`. Keep disconnect-order tracking until the winner call is acknowledged. ```json { "winner": "blue", "red_connected": false, "blue_connected": false, "red_score": 0, "blue_score": 2, "forfeit": true } ``` ### 4. Skip-forfeit (two skipped turns) Not a disconnect. If that player is still connected, keep `*_connected: true`. Send `forfeit: true`. They do **not** get CC. Send the actual score. Winner is whoever the game rules already pick. Hidden MMR uses the score table. ### 5. Process crash / forced shutdown after fees were collected Same as remaining-player or last-disconnect. Send accurate `*_connected`, scores, `forfeit: true` if it was a disconnect path, and call winner **before** exit whenever possible. Never skip the winner call after collect. Never invent a draw. ## Success response (ignore MMR on clients) ```json { "ok": true, "id": 123, "winner": "red", "winner_id": 456, "already_settled": false, "economy": { "entry_fee_rc": 21, "rc_prize": 34, "participant_cc": 100, "cc_awarded_red": true, "cc_awarded_blue": false }, "mmr_delta": 27, "forfeit": false } ``` - Keep using `economy` / `cc_awarded_*` as today. - `mmr_delta` and `forfeit` are for server logs only. **Do not forward them to player clients. Do not show them in UI.** - Honor response `winner` / `winner_id`. Simultaneous Leave/skip: the second `forfeit: true` PATCH is **200** `already_settled` with the first settle’s winner, not 409. HTTP: 200 = settled, already settled same winner, or a second forfeit that lost the race. 409 = entries not collected, or winner already set to the other side on a **non-forfeit** conflict. ## Suggested C# payload Extend the existing winner DTO / anonymous object. Do not stringify bools/ints. ```csharp public sealed class WinnerReportBody { public string winner; // "red" | "blue" public bool red_connected; public bool blue_connected; public int red_score; public int blue_score; public bool forfeit; } ``` Serialize with standard JSON (`true`/`false`, numeric scores). Field names must match exactly (snake_case as above). Find the dedicated-server method that currently PATCHes `/internal/match/{id}/winner` and add `red_score`, `blue_score`, `forfeit` there. Pass the live round scoreboard and whether this settle is a leave, disconnect, or skip/auto-forfeit. ## Do not - Show MMR, `mmr_delta`, or rating of any kind to players - Change join PATCH (`red_joined_at` / `blue_joined_at`) or rematch - Omit scores on forfeit (still send the score at leave) - Set `forfeit: false` for skip-forfeit / auto-forfeit (must be `true` so the skipper gets no CC) - Infer `red_connected` / `blue_connected` from who won - Leave a collected match without a winner call ## Done when - [ ] Winner PATCH body always includes `red_score`, `blue_score`, `forfeit` - [ ] Score wins send `forfeit: false` and the real 3–0 / 3–1 / 3–2 - [ ] Leave / disconnect / last-disconnect send `forfeit: true` and `*_connected: false` on leavers - [ ] Skip-forfeit / auto-forfeit sends `forfeit: true` (skipper gets no CC even if still connected) - [ ] No client UI or gameplay packet exposes MMR - [ ] Winner is still always called after fees were collected