Files
kickkingsapi/docs/unity_cc_forfeit_prompt.md
T
2026-09-04 19:11:49 +00:00

181 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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