# Prompt for Unity Cursor — no CC on forfeit (including skip/auto-forfeit) Copy everything below the line into Unity Cursor. Do not change join/collect, rematch, hidden MMR display, or player-facing UI. --- You are working in the Kick Kings **dedicated server** Unity project. The matchmaker already awards 100 CC only to players still **connected** at settle. That is not enough: a skip-forfeit / auto-forfeit player stays connected, so they still received CC. The API now **also** withholds CC from anyone who forfeited. Your only job: on `PATCH /internal/match/{matchId}/winner`, send `forfeit: true` for **every** forfeit path — leave, disconnect, last-disconnect-wins, **and skip/auto-forfeit (two skipped turns)**. Keep `red_connected` / `blue_connected` accurate. Do not infer them from `winner`. Keep the existing header `X-Dedicated-Server-Secret`. Call winner **exactly once** when the outcome is known. Do not reimplement CC. The API decides who gets it from the flags you send. ## Endpoint (unchanged URL) `PATCH /internal/match/{matchId}/winner` ## JSON body (already exists — fix `forfeit` on skip/auto-forfeit) Always send all six fields. JSON types must be real booleans and integers (`true` not `"true"`, `3` not `"3"`). ```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. RC prize still goes here. 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. | | `blue_score` | int | Same for blue. | | `forfeit` | bool | `true` for leave, disconnect, last-disconnect-wins, **and skip/auto-forfeit**. `false` **only** for a normal score win. | ## What changed (this is the bug) Previous contract: skip-forfeit sent `forfeit: false` because the skipper was still connected. The API then paid them 100 CC. New contract: skip-forfeit / auto-forfeit is still not a disconnect (`*_connected` stays `true` if they are in the match), but it **is** a forfeit. Send `forfeit: true`. The API then pays CC to the connected **non-forfeiting** side only. ## How the API awards CC (do not reimplement) Let `amount` = 100. Do **not** infer connectedness from `winner`. A last-disconnect winner can be disconnected: they get RC, not CC. On any forfeit, CC is never paid to the **loser**. | `red_connected` | `blue_connected` | `forfeit` | CC credit | |-----------------|------------------|-----------|-----------| | true | true | false | +amount to **both** | | true | true | true | +amount to the **winner only** (skip/auto-forfeit) | | true | false | true | +amount to **red only** | | false | true | true | +amount to **blue only** | | false | false | true | **nobody** | RC prize / escrow is unchanged: still paid to `winner`. ## 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. Skip-forfeit is **not** a disconnect. If that player is still in the match, keep their `*_connected: true` **and** send `forfeit: true`. ## What to send in each end condition ### 1. Normal score win (first to 3) Both still in the match. `forfeit: false`. Both get CC. ```json { "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 3, "blue_score": 1, "forfeit": false } ``` ### 2. One player leaves or disconnects Remaining player wins. Leaver `*_connected: false`. `forfeit: true`. Only the remaining connected player gets CC. ```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`. Nobody gets CC. RC still goes to `winner`. ```json { "winner": "blue", "red_connected": false, "blue_connected": false, "red_score": 0, "blue_score": 2, "forfeit": true } ``` ### 4. Skip-forfeit / auto-forfeit (two skipped turns) — this is the change Not a disconnect. Keep `*_connected: true` if they are still in the match. Send `forfeit: true`. Winner is whoever the game rules already pick. The skipper (loser) does **not** get CC. The opponent gets CC if connected. ```json { "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 1, "blue_score": 0, "forfeit": true } ``` ### 5. Process crash / forced shutdown after fees were collected Same as remaining-player or last-disconnect. Send accurate `*_connected`, scores, and `forfeit: true` if it was a disconnect/forfeit path. Call winner **before** exit whenever possible. Never skip the winner call after collect. Never invent a draw. ## Success response (use `cc_awarded_*` as today) ```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": true } ``` - Keep using `economy.cc_awarded_red` / `cc_awarded_blue` as today. - `mmr_delta` and `forfeit` on the response are for server logs only. **Do not forward them to player clients. Do not show them in UI.** - Honor response `winner` / `winner_id`. On simultaneous Leave/skip, a second `forfeit: true` PATCH returns **200** `already_settled: true` 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. ## Where to change Find the dedicated-server method that PATCHes `/internal/match/{id}/winner`. Today skip-forfeit / two skipped turns likely sets `forfeit = false` because the player is still connected. Change that path so `forfeit = true`. Leave, disconnect, and last-disconnect should already send `forfeit: true`. Do not regress those. Payload shape is unchanged: ```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; } ``` ## Do not - Set `forfeit: false` for skip-forfeit / auto-forfeit - Set `*_connected: false` for skip-forfeit just to kill CC (the player is still in the match; that would also switch hidden MMR to the 20% disconnect path) - Infer `red_connected` / `blue_connected` from who won - Change join PATCH (`red_joined_at` / `blue_joined_at`) or rematch - Show MMR, `mmr_delta`, or CC award flags in player UI unless they are already shown - Skip the winner call after fees were collected ## Done when - [ ] Normal score wins still send `forfeit: false` - [ ] Leave / disconnect / last-disconnect still send `forfeit: true` and `*_connected: false` on leavers - [ ] Skip-forfeit / auto-forfeit sends `forfeit: true` with accurate `*_connected` (usually both `true`) - [ ] Skipper no longer receives CC; opponent still does if connected - [ ] Winner is still always called after fees were collected