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
+165
View File
@@ -0,0 +1,165 @@
# 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 (03 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 (30) | 100% |
| Normal finish, loser score 1 (31) | 90% |
| Normal finish, loser score 2+ (32) | 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 30 (100%). That is wrong for 31 and 32. **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: 30, 31, or 32. `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 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.
## 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 30 / 31 / 32
- [ ] 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