This commit is contained in:
API runner
2026-09-04 19:11:49 +00:00
parent b933f09187
commit 3d063af9ae
130 changed files with 4876 additions and 53 deletions
+180
View File
@@ -0,0 +1,180 @@
# 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** settles 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